feat(api): 开发auth、login等接口,规范格式化
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const APP_CONFIG = Symbol('APP_CONFIG');
|
||||
|
||||
function findEnvironmentFile(): string {
|
||||
const directories = [
|
||||
process.cwd(),
|
||||
resolve(process.cwd(), '..'),
|
||||
resolve(process.cwd(), '../..'),
|
||||
];
|
||||
const environmentFile = directories
|
||||
.map((directory) => resolve(directory, '.env'))
|
||||
.find((file) => existsSync(file));
|
||||
|
||||
return environmentFile ?? resolve(process.cwd(), '.env');
|
||||
}
|
||||
|
||||
// This schema is the only supported source for API runtime settings. Defaults are
|
||||
// intentionally limited to non-secret local-development values.
|
||||
const environmentSchema = z.object({
|
||||
NODE_ENV: z
|
||||
.enum(['development', 'test', 'production'])
|
||||
.default('development'),
|
||||
API_PORT: z.coerce.number().int().min(1).max(65_535).default(8200),
|
||||
WEB_ORIGIN: z.url().default('http://localhost:8100'),
|
||||
DATABASE_URL: z.url(),
|
||||
REDIS_URL: z.url(),
|
||||
AUTH_JWT_SECRET: z.string().min(32),
|
||||
AUTH_RATE_LIMIT_KEY_SECRET: z.string().min(32),
|
||||
AUTH_PASSWORD_MIN_LENGTH: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(12)
|
||||
.max(256)
|
||||
.default(12),
|
||||
API_BODY_LIMIT_BYTES: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(1_024)
|
||||
.max(10 * 1024 * 1024)
|
||||
.default(1_048_576),
|
||||
AUTH_RATE_LIMIT_WINDOW_SECONDS: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(86_400)
|
||||
.default(900),
|
||||
AUTH_LOGIN_IP_LIMIT: z.coerce.number().int().min(1).max(1000).default(10),
|
||||
AUTH_LOGIN_EMAIL_LIMIT: z.coerce.number().int().min(1).max(1000).default(5),
|
||||
AUTH_REGISTER_IP_LIMIT: z.coerce.number().int().min(1).max(1000).default(5),
|
||||
AUTH_REGISTER_EMAIL_LIMIT: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(1000)
|
||||
.default(3),
|
||||
AUTH_REFRESH_IP_LIMIT: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(10_000)
|
||||
.default(30),
|
||||
TRUSTED_PROXY_CIDRS: z.string().default(''),
|
||||
SWAGGER_ENABLED: z.enum(['true', 'false']).default('true'),
|
||||
LOG_LEVEL: z.enum(['error', 'warn', 'log', 'debug']).default('log'),
|
||||
});
|
||||
|
||||
type Environment = z.infer<typeof environmentSchema>;
|
||||
export type AppConfig = Omit<
|
||||
Environment,
|
||||
'TRUSTED_PROXY_CIDRS' | 'SWAGGER_ENABLED'
|
||||
> & {
|
||||
trustedProxyCidrs: string[];
|
||||
swaggerEnabled: boolean;
|
||||
};
|
||||
|
||||
function validateEnvironment(
|
||||
environment: Record<string, unknown>,
|
||||
): Environment {
|
||||
const result = environmentSchema.safeParse(environment);
|
||||
if (result.success) return result.data;
|
||||
// Fail before Nest opens a listener; a half-configured API is unsafe to run.
|
||||
throw new Error(
|
||||
`Invalid environment configuration: ${result.error.issues.map((issue) => issue.path.join('.')).join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
function createAppConfig(environment: Environment): AppConfig {
|
||||
return {
|
||||
...environment,
|
||||
// Express only trusts forwarding headers when this explicit allowlist is non-empty.
|
||||
trustedProxyCidrs: environment.TRUSTED_PROXY_CIDRS.split(',')
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean),
|
||||
swaggerEnabled: environment.SWAGGER_ENABLED === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
cache: true,
|
||||
// Turbo runs this workspace's scripts from apps/api, while the
|
||||
// shared development environment lives at the repository root.
|
||||
envFilePath: findEnvironmentFile(),
|
||||
}),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_CONFIG,
|
||||
// Export a normalized immutable config object so services never read process.env directly.
|
||||
useFactory: (): AppConfig =>
|
||||
createAppConfig(validateEnvironment(process.env)),
|
||||
},
|
||||
],
|
||||
exports: [APP_CONFIG],
|
||||
})
|
||||
export class AppConfigModule {}
|
||||
Reference in New Issue
Block a user