diff --git a/.env b/.env index 1caeecb..d623456 100644 --- a/.env +++ b/.env @@ -8,7 +8,7 @@ DB_NAME='auth_db' # github GITHUB_CLIENT_ID=Ov23lihk723FlNAwlFg6 GITHUB_CLIENT_SECRET=b839f50bba1f006ffdd43fb73c5ae221a54e1e2e -GITHUB_CALLBACK_URL=http://108.171.193.155:8000/callback +GITHUB_CALLBACK_URL=http://45.130.23.71:8000/callback # 飞书 diff --git a/README.md b/README.md index f5aa86c..8726349 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,96 @@ -
+# OAuth NestJS Demo -[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 -[circleci-url]: https://circleci.com/gh/nestjs/nest +A NestJS backend with multi-provider OAuth, JWT authentication, task management, and a license/activation key system. -A progressive Node.js framework for building efficient and scalable server-side applications.
- - +## Tech Stack -## Description +- **Framework**: NestJS 10 + TypeScript +- **Database**: MySQL + TypeORM +- **Auth**: JWT, GitHub OAuth2, Lark OAuth2 +- **API Docs**: Swagger (`/api`) +- **Package Manager**: pnpm -[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. - -## Installation +## Getting Started ```bash -$ pnpm install +# Install dependencies +pnpm install + +# Configure environment variables +cp .env.example .env # then edit .env + +# Start in development mode +pnpm run start:dev ``` -## Running the app +See [docs/QUICKSTART.md](docs/QUICKSTART.md) for a detailed setup guide. + +## Documentation + +| Document | Description | +| --- | --- | +| [QUICKSTART.md](docs/QUICKSTART.md) | Quick start guide | +| [OAuth-JWT-Integration.md](docs/OAuth-JWT-Integration.md) | OAuth and JWT integration | +| [database-flow.md](docs/database-flow.md) | Database schema and data flow | +| [LICENSE_SYSTEM.md](docs/LICENSE_SYSTEM.md) | License system overview | +| [LICENSE_IMPLEMENTATION.md](docs/LICENSE_IMPLEMENTATION.md) | License implementation details | +| [GENERATE_LICENSE_GUIDE.md](docs/GENERATE_LICENSE_GUIDE.md) | License generation guide | +| [HOW_TO_GENERATE.md](docs/HOW_TO_GENERATE.md) | License generation instructions | + +## Scripts + +| Script | Description | +| --- | --- | +| `scripts/generate-licenses.sh` | Interactive license key generation | +| `scripts/test-license-system.sh` | License system integration tests | +| `scripts/check-db-schema.js` | Database schema validation | ```bash -# development -$ pnpm run start +# Generate license keys +bash scripts/generate-licenses.sh -# watch mode -$ pnpm run start:dev - -# production mode -$ pnpm run start:prod +# Run license system tests +bash scripts/test-license-system.sh ``` -## Test +## Running the App ```bash -# unit tests -$ pnpm run test +# Development +pnpm run start:dev -# e2e tests -$ pnpm run test:e2e - -# test coverage -$ pnpm run test:cov +# Production build +pnpm run build +pnpm run start:prod ``` -## Support +## Tests -Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). +```bash +# Unit tests +pnpm run test -## Stay in touch +# E2E tests +pnpm run test:e2e -- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) -- Website - [https://nestjs.com](https://nestjs.com/) -- Twitter - [@nestframework](https://twitter.com/nestframework) +# Coverage +pnpm run test:cov +``` -## License +## Project Structure -Nest is [MIT licensed](LICENSE). +```text +src/ +├── auth/ # JWT auth, strategies, guards +├── common/ # Shared filters, interceptors, guards +├── user/ # User management +├── task/ # Task management +├── license/ # License/activation key system +├── github/ # GitHub OAuth provider +├── lark/ # Lark OAuth provider +├── deepseek/ # DeepSeek AI provider +└── constants/ # Shared constants +docs/ # Project documentation +scripts/ # Utility and test scripts +test/ # E2E tests +``` diff --git a/GENERATE_LICENSE_GUIDE.md b/docs/GENERATE_LICENSE_GUIDE.md similarity index 100% rename from GENERATE_LICENSE_GUIDE.md rename to docs/GENERATE_LICENSE_GUIDE.md diff --git a/HOW_TO_GENERATE.md b/docs/HOW_TO_GENERATE.md similarity index 100% rename from HOW_TO_GENERATE.md rename to docs/HOW_TO_GENERATE.md diff --git a/LICENSE_IMPLEMENTATION.md b/docs/LICENSE_IMPLEMENTATION.md similarity index 100% rename from LICENSE_IMPLEMENTATION.md rename to docs/LICENSE_IMPLEMENTATION.md diff --git a/LICENSE_SYSTEM.md b/docs/LICENSE_SYSTEM.md similarity index 100% rename from LICENSE_SYSTEM.md rename to docs/LICENSE_SYSTEM.md diff --git a/OAuth-JWT-Integration.md b/docs/OAuth-JWT-Integration.md similarity index 100% rename from OAuth-JWT-Integration.md rename to docs/OAuth-JWT-Integration.md diff --git a/QUICKSTART.md b/docs/QUICKSTART.md similarity index 100% rename from QUICKSTART.md rename to docs/QUICKSTART.md diff --git a/database-flow.md b/docs/database-flow.md similarity index 100% rename from database-flow.md rename to docs/database-flow.md diff --git a/check-db-schema.js b/scripts/check-db-schema.js similarity index 100% rename from check-db-schema.js rename to scripts/check-db-schema.js diff --git a/generate-licenses.sh b/scripts/generate-licenses.sh similarity index 58% rename from generate-licenses.sh rename to scripts/generate-licenses.sh index 6bf4521..87ddfe9 100755 --- a/generate-licenses.sh +++ b/scripts/generate-licenses.sh @@ -38,7 +38,7 @@ if [ "$has_admin" != "y" ]; then \"email\": \"$admin_email\" }") - ADMIN_TOKEN=$(echo $REGISTER_RESPONSE | jq -r '.access_token') + ADMIN_TOKEN=$(echo $REGISTER_RESPONSE | jq -r '.data.access_token') if [ "$ADMIN_TOKEN" = "null" ] || [ -z "$ADMIN_TOKEN" ]; then echo -e "${RED}注册失败!${NC}" @@ -64,7 +64,7 @@ else \"password\": \"$admin_password\" }") - ADMIN_TOKEN=$(echo $LOGIN_RESPONSE | jq -r '.access_token') + ADMIN_TOKEN=$(echo $LOGIN_RESPONSE | jq -r '.data.access_token') if [ "$ADMIN_TOKEN" = "null" ] || [ -z "$ADMIN_TOKEN" ]; then echo -e "${RED}登录失败!请检查用户名和密码${NC}" @@ -126,11 +126,17 @@ GENERATE_RESPONSE=$(curl -s -X POST "$BASE_URL/license/generate" \ }") # 检查是否成功 -if echo "$GENERATE_RESPONSE" | jq -e '.[0].code' > /dev/null 2>&1; then - echo -e "${GREEN}卡密生成成功!${NC}" +if echo "$GENERATE_RESPONSE" | jq -e '.data[0].code' > /dev/null 2>&1; then + LICENSES=$(echo "$GENERATE_RESPONSE" | jq -r '.data') + ACTUAL_COUNT=$(echo "$LICENSES" | jq 'length') + + echo -e "${GREEN}✓ 卡密生成成功!共 $ACTUAL_COUNT 个${NC}" echo "" - echo "生成的卡密:" - echo "$GENERATE_RESPONSE" | jq -r '.[] | " \(.code) - \(.type) (\(.validDays)天) - \(.remarks // "无备注")"' + echo -e " ┌─────────────────────┬──────────┬────────┬──────────────┐" + echo -e " │ 卡密 │ 类型 │ 有效期 │ 备注 │" + echo -e " ├─────────────────────┼──────────┼────────┼──────────────┤" + echo "$LICENSES" | jq -r '.[] | " │ \(.code) │ \(.type | if . == "trial" then "试用卡 " elif . == "monthly" then "月卡 " elif . == "yearly" then "年卡 " else "终身卡 " end) │ \(.validDays)天 │ \(.remarks // "无备注 ") │"' + echo -e " └─────────────────────┴──────────┴────────┴──────────────┘" echo "" # 询问是否保存到文件 @@ -138,16 +144,20 @@ if echo "$GENERATE_RESPONSE" | jq -e '.[0].code' > /dev/null 2>&1; then if [ "$save_to_file" = "y" ]; then FILENAME="licenses_$(date +%Y%m%d_%H%M%S).txt" - echo "生成时间: $(date)" > "$FILENAME" - echo "类型: $LICENSE_TYPE" >> "$FILENAME" - echo "有效天数: $VALID_DAYS" >> "$FILENAME" - echo "数量: $count" >> "$FILENAME" - echo "备注: $remarks" >> "$FILENAME" - echo "" >> "$FILENAME" - echo "卡密列表:" >> "$FILENAME" - echo "$GENERATE_RESPONSE" | jq -r '.[] | .code' >> "$FILENAME" - - echo -e "${GREEN}卡密已保存到: $FILENAME${NC}" + { + echo "===============================" + echo " 卡密生成记录" + echo "===============================" + echo " 生成时间: $(date '+%Y-%m-%d %H:%M:%S')" + echo " 类型: $LICENSE_TYPE" + echo " 有效天数: ${VALID_DAYS}天" + echo " 数量: $ACTUAL_COUNT 个" + echo " 备注: ${remarks:-无}" + echo "===============================" + echo "" + echo "$LICENSES" | jq -r '.[] | .code' + } > "$FILENAME" + echo -e "${GREEN}✓ 卡密已保存到: $FILENAME${NC}" fi # 显示统计信息 @@ -155,16 +165,27 @@ if echo "$GENERATE_RESPONSE" | jq -e '.[0].code' > /dev/null 2>&1; then read -p "是否查看卡密统计信息?(y/n): " show_stats if [ "$show_stats" = "y" ]; then - echo -e "${YELLOW}查询统计信息...${NC}" STATS=$(curl -s -X GET "$BASE_URL/license/statistics" \ -H "Authorization: Bearer $ADMIN_TOKEN") - - echo "$STATS" | jq '.' + TOTAL=$(echo "$STATS" | jq -r '.data.total') + UNUSED=$(echo "$STATS" | jq -r '.data.unused') + ACTIVE=$(echo "$STATS" | jq -r '.data.active') + EXPIRED=$(echo "$STATS"| jq -r '.data.expired') + REVOKED=$(echo "$STATS"| jq -r '.data.revoked') + echo "" + echo -e " ${YELLOW}── 卡密统计 ──────────────────${NC}" + echo -e " 总数: $TOTAL" + echo -e " 未使用: ${GREEN}$UNUSED${NC}" + echo -e " 已激活: $ACTIVE" + echo -e " 已过期: $EXPIRED" + echo -e " 已撤销: ${RED}$REVOKED${NC}" + echo -e " ${YELLOW}──────────────────────────────${NC}" fi else - echo -e "${RED}生成失败!${NC}" - echo "$GENERATE_RESPONSE" | jq '.' + echo -e "${RED}✗ 生成失败!${NC}" + ERR_MSG=$(echo "$GENERATE_RESPONSE" | jq -r '.message // .data // .') + echo -e " 原因: $ERR_MSG" exit 1 fi diff --git a/test-license-system.sh b/scripts/test-license-system.sh similarity index 100% rename from test-license-system.sh rename to scripts/test-license-system.sh diff --git a/src/common/guards/admin.guard.ts b/src/common/guards/admin.guard.ts index 546054a..4266719 100644 --- a/src/common/guards/admin.guard.ts +++ b/src/common/guards/admin.guard.ts @@ -14,11 +14,11 @@ export class AdminGuard implements CanActivate { const request = context.switchToHttp().getRequest(); const user = request.user; - if (!user || !user.userId) { + if (!user || !user.id) { throw new ForbiddenException('请先登录'); } - const isAdmin = await this.adminService.isAdmin(user.userId); + const isAdmin = await this.adminService.isAdmin(user.id); if (!isAdmin) { throw new ForbiddenException('需要管理员权限'); diff --git a/src/common/services/admin.service.ts b/src/common/services/admin.service.ts index 6cc5f85..2ff5196 100644 --- a/src/common/services/admin.service.ts +++ b/src/common/services/admin.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { User } from '../user/entities/user.entity'; +import { User } from '../../user/entities/user.entity'; @Injectable() export class AdminService { diff --git a/src/license/guards/license.guard.ts b/src/license/guards/license.guard.ts index 3637dde..e6f9a95 100644 --- a/src/license/guards/license.guard.ts +++ b/src/license/guards/license.guard.ts @@ -28,12 +28,12 @@ export class LicenseGuard implements CanActivate { const request = context.switchToHttp().getRequest(); const user = request.user; - if (!user || !user.userId) { + if (!user || !user.id) { throw new ForbiddenException('请先登录'); } const hasValidLicense = await this.licenseService.verifyUserLicense( - user.userId, + user.id, ); if (!hasValidLicense) {