@svton/cli 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/index.d.mts +1 -1
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +8 -5
  4. package/dist/index.mjs +8 -5
  5. package/package.json +2 -1
  6. package/templates/apps/admin/next-env.d.ts +2 -0
  7. package/templates/apps/admin/next.config.js +15 -0
  8. package/templates/apps/admin/package.json.tpl +54 -0
  9. package/templates/apps/admin/postcss.config.js +6 -0
  10. package/templates/apps/admin/src/app/globals.css +37 -0
  11. package/templates/apps/admin/src/app/layout.tsx +19 -0
  12. package/templates/apps/admin/src/app/login/page.tsx +96 -0
  13. package/templates/apps/admin/src/app/page.tsx +8 -0
  14. package/templates/apps/admin/src/app/users/page.tsx +165 -0
  15. package/templates/apps/admin/src/components/ui/switch.tsx +29 -0
  16. package/templates/apps/admin/src/hooks/useAPI.ts +130 -0
  17. package/templates/apps/admin/src/lib/api-client.ts +100 -0
  18. package/templates/apps/admin/tailwind.config.js +54 -0
  19. package/templates/apps/admin/tsconfig.json +22 -0
  20. package/templates/apps/backend/.env.example +14 -0
  21. package/templates/apps/backend/nest-cli.json +8 -0
  22. package/templates/apps/backend/package.json.tpl +57 -0
  23. package/templates/apps/backend/prisma/schema.prisma +72 -0
  24. package/templates/apps/backend/prisma/seed.ts +32 -0
  25. package/templates/apps/backend/src/app.controller.ts +15 -0
  26. package/templates/apps/backend/src/app.module.ts +19 -0
  27. package/templates/apps/backend/src/app.service.ts +12 -0
  28. package/templates/apps/backend/src/auth/auth.controller.ts +31 -0
  29. package/templates/apps/backend/src/auth/auth.module.ts +27 -0
  30. package/templates/apps/backend/src/auth/auth.service.ts +89 -0
  31. package/templates/apps/backend/src/auth/jwt-auth.guard.ts +5 -0
  32. package/templates/apps/backend/src/auth/jwt.strategy.ts +27 -0
  33. package/templates/apps/backend/src/main.ts +40 -0
  34. package/templates/apps/backend/src/prisma/prisma.module.ts +9 -0
  35. package/templates/apps/backend/src/prisma/prisma.service.ts +13 -0
  36. package/templates/apps/backend/src/user/user.controller.ts +50 -0
  37. package/templates/apps/backend/src/user/user.module.ts +12 -0
  38. package/templates/apps/backend/src/user/user.service.ts +117 -0
  39. package/templates/apps/backend/tsconfig.json +23 -0
  40. package/templates/apps/mobile/babel.config.js +8 -0
  41. package/templates/apps/mobile/config/index.ts +65 -0
  42. package/templates/apps/mobile/package.json.tpl +48 -0
  43. package/templates/apps/mobile/project.config.json.tpl +17 -0
  44. package/templates/apps/mobile/src/app.config.ts +9 -0
  45. package/templates/apps/mobile/src/app.scss +4 -0
  46. package/templates/apps/mobile/src/app.ts +8 -0
  47. package/templates/apps/mobile/src/pages/index/index.scss +7 -0
  48. package/templates/apps/mobile/src/pages/index/index.tsx +49 -0
  49. package/templates/apps/mobile/tsconfig.json +21 -0
  50. package/templates/packages/types/package.json.tpl +16 -0
  51. package/templates/packages/types/src/api.ts +88 -0
  52. package/templates/packages/types/src/common.ts +89 -0
  53. package/templates/packages/types/src/index.ts +3 -0
  54. package/templates/packages/types/tsconfig.json +16 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Admin 端 API Client
3
+ * 使用优化后的 @svton/api-client 系统
4
+ */
5
+
6
+ import axios from 'axios';
7
+ import {
8
+ createApiClient,
9
+ createTokenInterceptor,
10
+ createUnauthorizedInterceptor,
11
+ } from '@svton/api-client';
12
+ // 引入类型定义以启用模块增强
13
+ import '@svton/types';
14
+
15
+ /**
16
+ * Axios 适配器
17
+ * 后端使用统一响应格式:{ code, data, message }
18
+ */
19
+ const axiosAdapter = {
20
+ async request(config: any) {
21
+ const response = await axios.request(config);
22
+ // 后端 ResponseInterceptor 包装格式:{ code: 0, data: {...}, message: 'success' }
23
+ // 需要提取其中的 data 字段
24
+ if (response.data && typeof response.data === 'object' && 'data' in response.data) {
25
+ return response.data.data;
26
+ }
27
+ return response.data;
28
+ },
29
+ };
30
+
31
+ /**
32
+ * 创建 API 客户端实例
33
+ */
34
+ const { api, apiAsync, runGenerator } = createApiClient(axiosAdapter, {
35
+ baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api',
36
+ interceptors: {
37
+ // 请求拦截器 - 添加 Token
38
+ request: [
39
+ createTokenInterceptor(() => {
40
+ if (typeof window !== 'undefined') {
41
+ // 从 zustand persist 存储中读取 token
42
+ const authStorage = localStorage.getItem('auth-storage');
43
+ if (authStorage) {
44
+ try {
45
+ const { state } = JSON.parse(authStorage);
46
+ return state?.accessToken || null;
47
+ } catch (e) {
48
+ console.error('[API Client] Failed to parse auth-storage:', e);
49
+ }
50
+ }
51
+ }
52
+ return null;
53
+ }),
54
+ ],
55
+ // 错误拦截器 - 处理 401
56
+ error: [
57
+ createUnauthorizedInterceptor(() => {
58
+ if (typeof window !== 'undefined') {
59
+ // 清除 zustand persist 存储
60
+ localStorage.removeItem('auth-storage');
61
+ window.location.href = '/login';
62
+ }
63
+ }),
64
+ ],
65
+ },
66
+ });
67
+
68
+ // 导出 API 函数
69
+ export { api, apiAsync, runGenerator };
70
+
71
+ /**
72
+ * 使用示例:
73
+ *
74
+ * // Generator 方式(推荐)
75
+ * function* loadDashboard() {
76
+ * const user = yield* api('GET:/auth/me')
77
+ * const contents = yield* api('GET:/contents', { page: 1, pageSize: 10 })
78
+ * return { user, contents }
79
+ * }
80
+ *
81
+ * const data = await runGenerator(loadDashboard())
82
+ *
83
+ * // Promise 方式
84
+ * const user = await apiAsync('GET:/auth/me')
85
+ * const contents = await apiAsync('GET:/contents', { page: 1 })
86
+ *
87
+ * // 创建内容
88
+ * const newContent = await apiAsync('POST:/contents', {
89
+ * title: '标题',
90
+ * body: '内容',
91
+ * contentType: 'article',
92
+ * categoryId: 1,
93
+ * })
94
+ *
95
+ * // 更新内容
96
+ * const updated = await apiAsync('PUT:/contents/:id', {
97
+ * id: 123,
98
+ * data: { title: '新标题' }
99
+ * })
100
+ */
@@ -0,0 +1,54 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ darkMode: ['class'],
4
+ content: [
5
+ './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
6
+ './src/components/**/*.{js,ts,jsx,tsx,mdx}',
7
+ './src/app/**/*.{js,ts,jsx,tsx,mdx}',
8
+ ],
9
+ theme: {
10
+ extend: {
11
+ colors: {
12
+ border: 'hsl(var(--border))',
13
+ input: 'hsl(var(--input))',
14
+ ring: 'hsl(var(--ring))',
15
+ background: 'hsl(var(--background))',
16
+ foreground: 'hsl(var(--foreground))',
17
+ primary: {
18
+ DEFAULT: 'hsl(var(--primary))',
19
+ foreground: 'hsl(var(--primary-foreground))',
20
+ },
21
+ secondary: {
22
+ DEFAULT: 'hsl(var(--secondary))',
23
+ foreground: 'hsl(var(--secondary-foreground))',
24
+ },
25
+ destructive: {
26
+ DEFAULT: 'hsl(var(--destructive))',
27
+ foreground: 'hsl(var(--destructive-foreground))',
28
+ },
29
+ muted: {
30
+ DEFAULT: 'hsl(var(--muted))',
31
+ foreground: 'hsl(var(--muted-foreground))',
32
+ },
33
+ accent: {
34
+ DEFAULT: 'hsl(var(--accent))',
35
+ foreground: 'hsl(var(--accent-foreground))',
36
+ },
37
+ popover: {
38
+ DEFAULT: 'hsl(var(--popover))',
39
+ foreground: 'hsl(var(--popover-foreground))',
40
+ },
41
+ card: {
42
+ DEFAULT: 'hsl(var(--card))',
43
+ foreground: 'hsl(var(--card-foreground))',
44
+ },
45
+ },
46
+ borderRadius: {
47
+ lg: 'var(--radius)',
48
+ md: 'calc(var(--radius) - 2px)',
49
+ sm: 'calc(var(--radius) - 4px)',
50
+ },
51
+ },
52
+ },
53
+ plugins: [require('tailwindcss-animate')],
54
+ };
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["dom", "dom.iterable", "esnext"],
4
+ "allowJs": true,
5
+ "skipLibCheck": true,
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "esModuleInterop": true,
9
+ "module": "esnext",
10
+ "moduleResolution": "bundler",
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "jsx": "preserve",
14
+ "incremental": true,
15
+ "plugins": [{ "name": "next" }],
16
+ "paths": {
17
+ "@/*": ["./src/*"]
18
+ }
19
+ },
20
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
21
+ "exclude": ["node_modules"]
22
+ }
@@ -0,0 +1,14 @@
1
+ # 数据库
2
+ DATABASE_URL="mysql://root:password@localhost:3306/mydb"
3
+
4
+ # Redis
5
+ REDIS_HOST=localhost
6
+ REDIS_PORT=6379
7
+ REDIS_PASSWORD=
8
+
9
+ # JWT
10
+ JWT_SECRET=your-jwt-secret-key
11
+ JWT_EXPIRES_IN=7d
12
+
13
+ # 服务端口
14
+ PORT=3000
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/nest-cli",
3
+ "collection": "@nestjs/schematics",
4
+ "sourceRoot": "src",
5
+ "compilerOptions": {
6
+ "deleteOutDir": true
7
+ }
8
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "{{ORG_NAME}}/backend",
3
+ "version": "1.0.0",
4
+ "description": "{{PROJECT_NAME}} 后端 API",
5
+ "prisma": {
6
+ "seed": "ts-node prisma/seed.ts"
7
+ },
8
+ "scripts": {
9
+ "build": "nest build",
10
+ "dev": "NODE_ENV=development nest start --watch",
11
+ "start": "NODE_ENV=production node dist/main",
12
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
13
+ "test": "jest",
14
+ "type-check": "tsc --noEmit",
15
+ "prisma:generate": "prisma generate",
16
+ "prisma:migrate": "prisma migrate dev",
17
+ "prisma:migrate:deploy": "prisma migrate deploy",
18
+ "prisma:seed": "ts-node prisma/seed.ts",
19
+ "prisma:studio": "prisma studio",
20
+ "db:init": "pnpm prisma:generate && pnpm prisma:migrate && pnpm prisma:seed",
21
+ "clean": "rm -rf dist"
22
+ },
23
+ "dependencies": {
24
+ "@nestjs/common": "^10.3.0",
25
+ "@nestjs/config": "^3.1.1",
26
+ "@nestjs/core": "^10.3.0",
27
+ "@nestjs/jwt": "^10.2.0",
28
+ "@nestjs/passport": "^10.0.3",
29
+ "@nestjs/platform-express": "^10.3.0",
30
+ "@nestjs/swagger": "^7.1.17",
31
+ "@prisma/client": "^5.7.1",
32
+ "@svton/types": "^1.0.0",
33
+ "bcrypt": "^5.1.1",
34
+ "class-transformer": "^0.5.1",
35
+ "class-validator": "^0.14.0",
36
+ "ioredis": "^5.3.2",
37
+ "passport": "^0.7.0",
38
+ "passport-jwt": "^4.0.1",
39
+ "reflect-metadata": "^0.2.1",
40
+ "rxjs": "^7.8.1"
41
+ },
42
+ "devDependencies": {
43
+ "@nestjs/cli": "^10.2.1",
44
+ "@nestjs/schematics": "^10.0.3",
45
+ "@nestjs/testing": "^10.2.10",
46
+ "@types/bcrypt": "^5.0.2",
47
+ "@types/express": "^4.17.21",
48
+ "@types/jest": "^29.5.11",
49
+ "@types/node": "^20.10.0",
50
+ "@types/passport-jwt": "^3.0.13",
51
+ "jest": "^29.7.0",
52
+ "prisma": "^5.7.0",
53
+ "ts-jest": "^29.1.1",
54
+ "ts-node": "^10.9.2",
55
+ "typescript": "^5.3.0"
56
+ }
57
+ }
@@ -0,0 +1,72 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "mysql"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ // 用户模型
11
+ model User {
12
+ id Int @id @default(autoincrement())
13
+ phone String @unique
14
+ password String
15
+ nickname String
16
+ avatar String?
17
+ email String?
18
+ bio String? @db.Text
19
+ role String @default("user") // user, admin, super_admin
20
+ status Int @default(1) // 0: 禁用, 1: 启用
21
+ lastLoginAt DateTime?
22
+ createdAt DateTime @default(now())
23
+ updatedAt DateTime @updatedAt
24
+
25
+ contents Content[]
26
+ comments Comment[]
27
+
28
+ @@index([phone])
29
+ @@index([role])
30
+ @@index([status])
31
+ @@map("users")
32
+ }
33
+
34
+ // 内容模型
35
+ model Content {
36
+ id Int @id @default(autoincrement())
37
+ title String
38
+ content String @db.Text
39
+ images String? @db.Text // JSON array
40
+ status String @default("draft") // draft, pending, published, rejected
41
+ viewCount Int @default(0)
42
+ likeCount Int @default(0)
43
+ commentCount Int @default(0)
44
+ authorId Int
45
+ author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
46
+ createdAt DateTime @default(now())
47
+ updatedAt DateTime @updatedAt
48
+
49
+ comments Comment[]
50
+
51
+ @@index([authorId])
52
+ @@index([status])
53
+ @@index([createdAt])
54
+ @@map("contents")
55
+ }
56
+
57
+ // 评论模型
58
+ model Comment {
59
+ id Int @id @default(autoincrement())
60
+ content String @db.Text
61
+ contentId Int
62
+ content_ Content @relation(fields: [contentId], references: [id], onDelete: Cascade)
63
+ authorId Int
64
+ author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
65
+ likeCount Int @default(0)
66
+ createdAt DateTime @default(now())
67
+ updatedAt DateTime @updatedAt
68
+
69
+ @@index([contentId])
70
+ @@index([authorId])
71
+ @@map("comments")
72
+ }
@@ -0,0 +1,32 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+
3
+ const prisma = new PrismaClient();
4
+
5
+ async function main() {
6
+ console.log('🌱 开始初始化数据...');
7
+
8
+ // 创建管理员用户
9
+ const admin = await prisma.user.upsert({
10
+ where: { phone: '13800000000' },
11
+ update: {},
12
+ create: {
13
+ phone: '13800000000',
14
+ password: '$2b$10$example', // 需要用 bcrypt 生成
15
+ nickname: '管理员',
16
+ role: 'admin',
17
+ status: 1,
18
+ },
19
+ });
20
+
21
+ console.log('✅ 管理员用户:', admin);
22
+ console.log('🎉 数据初始化完成!');
23
+ }
24
+
25
+ main()
26
+ .catch((e) => {
27
+ console.error(e);
28
+ process.exit(1);
29
+ })
30
+ .finally(async () => {
31
+ await prisma.$disconnect();
32
+ });
@@ -0,0 +1,15 @@
1
+ import { Controller, Get } from '@nestjs/common';
2
+ import { ApiTags, ApiOperation } from '@nestjs/swagger';
3
+ import { AppService } from './app.service';
4
+
5
+ @ApiTags('健康检查')
6
+ @Controller()
7
+ export class AppController {
8
+ constructor(private readonly appService: AppService) {}
9
+
10
+ @Get()
11
+ @ApiOperation({ summary: '健康检查' })
12
+ getHealth() {
13
+ return this.appService.getHealth();
14
+ }
15
+ }
@@ -0,0 +1,19 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { ConfigModule } from '@nestjs/config';
3
+ import { AppController } from './app.controller';
4
+ import { AppService } from './app.service';
5
+ import { PrismaModule } from './prisma/prisma.module';
6
+ import { AuthModule } from './auth/auth.module';
7
+ import { UserModule } from './user/user.module';
8
+
9
+ @Module({
10
+ imports: [
11
+ ConfigModule.forRoot({ isGlobal: true }),
12
+ PrismaModule,
13
+ AuthModule,
14
+ UserModule,
15
+ ],
16
+ controllers: [AppController],
17
+ providers: [AppService],
18
+ })
19
+ export class AppModule {}
@@ -0,0 +1,12 @@
1
+ import { Injectable } from '@nestjs/common';
2
+
3
+ @Injectable()
4
+ export class AppService {
5
+ getHealth() {
6
+ return {
7
+ status: 'ok',
8
+ timestamp: new Date().toISOString(),
9
+ environment: process.env.NODE_ENV || 'development',
10
+ };
11
+ }
12
+ }
@@ -0,0 +1,31 @@
1
+ import { Controller, Post, Body, Get, UseGuards, Request } from '@nestjs/common';
2
+ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
3
+ import { AuthService } from './auth.service';
4
+ import { JwtAuthGuard } from './jwt-auth.guard';
5
+ import type { LoginDto, RegisterDto } from '{{ORG_NAME}}/types';
6
+
7
+ @ApiTags('认证')
8
+ @Controller('auth')
9
+ export class AuthController {
10
+ constructor(private authService: AuthService) {}
11
+
12
+ @Post('login')
13
+ @ApiOperation({ summary: '登录' })
14
+ async login(@Body() dto: LoginDto) {
15
+ return this.authService.login(dto);
16
+ }
17
+
18
+ @Post('register')
19
+ @ApiOperation({ summary: '注册' })
20
+ async register(@Body() dto: RegisterDto) {
21
+ return this.authService.register(dto);
22
+ }
23
+
24
+ @Get('me')
25
+ @UseGuards(JwtAuthGuard)
26
+ @ApiBearerAuth()
27
+ @ApiOperation({ summary: '获取当前用户信息' })
28
+ async getProfile(@Request() req: any) {
29
+ return this.authService.validateUser(req.user.sub);
30
+ }
31
+ }
@@ -0,0 +1,27 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { JwtModule } from '@nestjs/jwt';
3
+ import { PassportModule } from '@nestjs/passport';
4
+ import { ConfigModule, ConfigService } from '@nestjs/config';
5
+ import { AuthController } from './auth.controller';
6
+ import { AuthService } from './auth.service';
7
+ import { JwtStrategy } from './jwt.strategy';
8
+ import { PrismaModule } from '../prisma/prisma.module';
9
+
10
+ @Module({
11
+ imports: [
12
+ PrismaModule,
13
+ PassportModule.register({ defaultStrategy: 'jwt' }),
14
+ JwtModule.registerAsync({
15
+ imports: [ConfigModule],
16
+ useFactory: async (configService: ConfigService) => ({
17
+ secret: configService.get<string>('JWT_SECRET'),
18
+ signOptions: { expiresIn: '7d' },
19
+ }),
20
+ inject: [ConfigService],
21
+ }),
22
+ ],
23
+ controllers: [AuthController],
24
+ providers: [AuthService, JwtStrategy],
25
+ exports: [AuthService],
26
+ })
27
+ export class AuthModule {}
@@ -0,0 +1,89 @@
1
+ import { Injectable, UnauthorizedException, ConflictException } from '@nestjs/common';
2
+ import { JwtService } from '@nestjs/jwt';
3
+ import * as bcrypt from 'bcrypt';
4
+ import { PrismaService } from '../prisma/prisma.service';
5
+ import type { LoginDto, RegisterDto, LoginVo } from '{{ORG_NAME}}/types';
6
+
7
+ @Injectable()
8
+ export class AuthService {
9
+ constructor(
10
+ private prisma: PrismaService,
11
+ private jwtService: JwtService,
12
+ ) {}
13
+
14
+ async login(dto: LoginDto): Promise<LoginVo> {
15
+ const user = await this.prisma.user.findUnique({
16
+ where: { phone: dto.phone },
17
+ });
18
+
19
+ if (!user) {
20
+ throw new UnauthorizedException('用户不存在');
21
+ }
22
+
23
+ const isPasswordValid = await bcrypt.compare(dto.password, user.password);
24
+ if (!isPasswordValid) {
25
+ throw new UnauthorizedException('密码错误');
26
+ }
27
+
28
+ if (user.status === 0) {
29
+ throw new UnauthorizedException('账号已被禁用');
30
+ }
31
+
32
+ const payload = { sub: user.id, phone: user.phone, role: user.role };
33
+ const accessToken = this.jwtService.sign(payload);
34
+ const refreshToken = this.jwtService.sign(payload, { expiresIn: '30d' });
35
+
36
+ return {
37
+ accessToken,
38
+ refreshToken,
39
+ user: {
40
+ id: user.id,
41
+ phone: user.phone,
42
+ nickname: user.nickname,
43
+ avatar: user.avatar,
44
+ role: user.role as any,
45
+ status: user.status as any,
46
+ createdAt: user.createdAt.toISOString(),
47
+ updatedAt: user.updatedAt.toISOString(),
48
+ },
49
+ };
50
+ }
51
+
52
+ async register(dto: RegisterDto): Promise<LoginVo> {
53
+ const existingUser = await this.prisma.user.findUnique({
54
+ where: { phone: dto.phone },
55
+ });
56
+
57
+ if (existingUser) {
58
+ throw new ConflictException('手机号已被注册');
59
+ }
60
+
61
+ const hashedPassword = await bcrypt.hash(dto.password, 10);
62
+
63
+ const user = await this.prisma.user.create({
64
+ data: {
65
+ phone: dto.phone,
66
+ password: hashedPassword,
67
+ nickname: dto.nickname,
68
+ role: 'user',
69
+ status: 1,
70
+ },
71
+ });
72
+
73
+ return this.login({ phone: dto.phone, password: dto.password });
74
+ }
75
+
76
+ async validateUser(userId: number) {
77
+ return this.prisma.user.findUnique({
78
+ where: { id: userId },
79
+ select: {
80
+ id: true,
81
+ phone: true,
82
+ nickname: true,
83
+ avatar: true,
84
+ role: true,
85
+ status: true,
86
+ },
87
+ });
88
+ }
89
+ }
@@ -0,0 +1,5 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { AuthGuard } from '@nestjs/passport';
3
+
4
+ @Injectable()
5
+ export class JwtAuthGuard extends AuthGuard('jwt') {}
@@ -0,0 +1,27 @@
1
+ import { Injectable, UnauthorizedException } from '@nestjs/common';
2
+ import { PassportStrategy } from '@nestjs/passport';
3
+ import { ExtractJwt, Strategy } from 'passport-jwt';
4
+ import { ConfigService } from '@nestjs/config';
5
+ import { AuthService } from './auth.service';
6
+
7
+ @Injectable()
8
+ export class JwtStrategy extends PassportStrategy(Strategy) {
9
+ constructor(
10
+ private configService: ConfigService,
11
+ private authService: AuthService,
12
+ ) {
13
+ super({
14
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
15
+ ignoreExpiration: false,
16
+ secretOrKey: configService.get<string>('JWT_SECRET'),
17
+ });
18
+ }
19
+
20
+ async validate(payload: any) {
21
+ const user = await this.authService.validateUser(payload.sub);
22
+ if (!user) {
23
+ throw new UnauthorizedException();
24
+ }
25
+ return { sub: payload.sub, phone: payload.phone, role: payload.role };
26
+ }
27
+ }
@@ -0,0 +1,40 @@
1
+ import { NestFactory } from '@nestjs/core';
2
+ import { ValidationPipe } from '@nestjs/common';
3
+ import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
4
+ import { AppModule } from './app.module';
5
+
6
+ async function bootstrap() {
7
+ const app = await NestFactory.create(AppModule);
8
+
9
+ // 全局验证管道
10
+ app.useGlobalPipes(
11
+ new ValidationPipe({
12
+ whitelist: true,
13
+ transform: true,
14
+ forbidNonWhitelisted: true,
15
+ }),
16
+ );
17
+
18
+ // CORS
19
+ app.enableCors({
20
+ origin: true,
21
+ credentials: true,
22
+ });
23
+
24
+ // Swagger 文档
25
+ const config = new DocumentBuilder()
26
+ .setTitle('API 文档')
27
+ .setDescription('项目 API 接口文档')
28
+ .setVersion('1.0')
29
+ .addBearerAuth()
30
+ .build();
31
+ const document = SwaggerModule.createDocument(app, config);
32
+ SwaggerModule.setup('api-docs', app, document);
33
+
34
+ const port = process.env.PORT || 3000;
35
+ await app.listen(port);
36
+ console.log(`🚀 Server running on http://localhost:${port}`);
37
+ console.log(`📚 API Docs: http://localhost:${port}/api-docs`);
38
+ }
39
+
40
+ bootstrap();
@@ -0,0 +1,9 @@
1
+ import { Global, Module } from '@nestjs/common';
2
+ import { PrismaService } from './prisma.service';
3
+
4
+ @Global()
5
+ @Module({
6
+ providers: [PrismaService],
7
+ exports: [PrismaService],
8
+ })
9
+ export class PrismaModule {}
@@ -0,0 +1,13 @@
1
+ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
2
+ import { PrismaClient } from '@prisma/client';
3
+
4
+ @Injectable()
5
+ export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
6
+ async onModuleInit() {
7
+ await this.$connect();
8
+ }
9
+
10
+ async onModuleDestroy() {
11
+ await this.$disconnect();
12
+ }
13
+ }