feat: 整理项目结构
This commit is contained in:
parent
aa62486f21
commit
8cd2cca57a
2
.env
2
.env
|
|
@ -8,7 +8,7 @@ DB_NAME='auth_db'
|
||||||
# github
|
# github
|
||||||
GITHUB_CLIENT_ID=Ov23lihk723FlNAwlFg6
|
GITHUB_CLIENT_ID=Ov23lihk723FlNAwlFg6
|
||||||
GITHUB_CLIENT_SECRET=b839f50bba1f006ffdd43fb73c5ae221a54e1e2e
|
GITHUB_CLIENT_SECRET=b839f50bba1f006ffdd43fb73c5ae221a54e1e2e
|
||||||
GITHUB_CALLBACK_URL=http://108.171.193.155:8000/callback
|
GITHUB_CALLBACK_URL=http://45.130.23.71:8000/callback
|
||||||
|
|
||||||
# 飞书
|
# 飞书
|
||||||
|
|
||||||
|
|
|
||||||
123
README.md
123
README.md
|
|
@ -1,73 +1,96 @@
|
||||||
<p align="center">
|
# OAuth NestJS Demo
|
||||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
A NestJS backend with multi-provider OAuth, JWT authentication, task management, and a license/activation key system.
|
||||||
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
|
||||||
|
|
||||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
## Tech Stack
|
||||||
<p align="center">
|
|
||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
|
||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
|
||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
|
||||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
|
||||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
|
||||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
|
||||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
|
||||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
|
||||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
|
||||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
|
||||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
|
||||||
</p>
|
|
||||||
<!--[](https://opencollective.com/nest#backer)
|
|
||||||
[](https://opencollective.com/nest#sponsor)-->
|
|
||||||
|
|
||||||
## 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.
|
## Getting Started
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
```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
|
```bash
|
||||||
# development
|
# Generate license keys
|
||||||
$ pnpm run start
|
bash scripts/generate-licenses.sh
|
||||||
|
|
||||||
# watch mode
|
# Run license system tests
|
||||||
$ pnpm run start:dev
|
bash scripts/test-license-system.sh
|
||||||
|
|
||||||
# production mode
|
|
||||||
$ pnpm run start:prod
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test
|
## Running the App
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# unit tests
|
# Development
|
||||||
$ pnpm run test
|
pnpm run start:dev
|
||||||
|
|
||||||
# e2e tests
|
# Production build
|
||||||
$ pnpm run test:e2e
|
pnpm run build
|
||||||
|
pnpm run start:prod
|
||||||
# test coverage
|
|
||||||
$ pnpm run test:cov
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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)
|
# Coverage
|
||||||
- Website - [https://nestjs.com](https://nestjs.com/)
|
pnpm run test:cov
|
||||||
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
```
|
||||||
|
|
||||||
## 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
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ if [ "$has_admin" != "y" ]; then
|
||||||
\"email\": \"$admin_email\"
|
\"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
|
if [ "$ADMIN_TOKEN" = "null" ] || [ -z "$ADMIN_TOKEN" ]; then
|
||||||
echo -e "${RED}注册失败!${NC}"
|
echo -e "${RED}注册失败!${NC}"
|
||||||
|
|
@ -64,7 +64,7 @@ else
|
||||||
\"password\": \"$admin_password\"
|
\"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
|
if [ "$ADMIN_TOKEN" = "null" ] || [ -z "$ADMIN_TOKEN" ]; then
|
||||||
echo -e "${RED}登录失败!请检查用户名和密码${NC}"
|
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
|
if echo "$GENERATE_RESPONSE" | jq -e '.data[0].code' > /dev/null 2>&1; then
|
||||||
echo -e "${GREEN}卡密生成成功!${NC}"
|
LICENSES=$(echo "$GENERATE_RESPONSE" | jq -r '.data')
|
||||||
|
ACTUAL_COUNT=$(echo "$LICENSES" | jq 'length')
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ 卡密生成成功!共 $ACTUAL_COUNT 个${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "生成的卡密:"
|
echo -e " ┌─────────────────────┬──────────┬────────┬──────────────┐"
|
||||||
echo "$GENERATE_RESPONSE" | jq -r '.[] | " \(.code) - \(.type) (\(.validDays)天) - \(.remarks // "无备注")"'
|
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 ""
|
echo ""
|
||||||
|
|
||||||
# 询问是否保存到文件
|
# 询问是否保存到文件
|
||||||
|
|
@ -138,16 +144,20 @@ if echo "$GENERATE_RESPONSE" | jq -e '.[0].code' > /dev/null 2>&1; then
|
||||||
|
|
||||||
if [ "$save_to_file" = "y" ]; then
|
if [ "$save_to_file" = "y" ]; then
|
||||||
FILENAME="licenses_$(date +%Y%m%d_%H%M%S).txt"
|
FILENAME="licenses_$(date +%Y%m%d_%H%M%S).txt"
|
||||||
echo "生成时间: $(date)" > "$FILENAME"
|
{
|
||||||
echo "类型: $LICENSE_TYPE" >> "$FILENAME"
|
echo "==============================="
|
||||||
echo "有效天数: $VALID_DAYS" >> "$FILENAME"
|
echo " 卡密生成记录"
|
||||||
echo "数量: $count" >> "$FILENAME"
|
echo "==============================="
|
||||||
echo "备注: $remarks" >> "$FILENAME"
|
echo " 生成时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
echo "" >> "$FILENAME"
|
echo " 类型: $LICENSE_TYPE"
|
||||||
echo "卡密列表:" >> "$FILENAME"
|
echo " 有效天数: ${VALID_DAYS}天"
|
||||||
echo "$GENERATE_RESPONSE" | jq -r '.[] | .code' >> "$FILENAME"
|
echo " 数量: $ACTUAL_COUNT 个"
|
||||||
|
echo " 备注: ${remarks:-无}"
|
||||||
echo -e "${GREEN}卡密已保存到: $FILENAME${NC}"
|
echo "==============================="
|
||||||
|
echo ""
|
||||||
|
echo "$LICENSES" | jq -r '.[] | .code'
|
||||||
|
} > "$FILENAME"
|
||||||
|
echo -e "${GREEN}✓ 卡密已保存到: $FILENAME${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 显示统计信息
|
# 显示统计信息
|
||||||
|
|
@ -155,16 +165,27 @@ if echo "$GENERATE_RESPONSE" | jq -e '.[0].code' > /dev/null 2>&1; then
|
||||||
read -p "是否查看卡密统计信息?(y/n): " show_stats
|
read -p "是否查看卡密统计信息?(y/n): " show_stats
|
||||||
|
|
||||||
if [ "$show_stats" = "y" ]; then
|
if [ "$show_stats" = "y" ]; then
|
||||||
echo -e "${YELLOW}查询统计信息...${NC}"
|
|
||||||
STATS=$(curl -s -X GET "$BASE_URL/license/statistics" \
|
STATS=$(curl -s -X GET "$BASE_URL/license/statistics" \
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN")
|
-H "Authorization: Bearer $ADMIN_TOKEN")
|
||||||
|
TOTAL=$(echo "$STATS" | jq -r '.data.total')
|
||||||
echo "$STATS" | jq '.'
|
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
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
echo -e "${RED}生成失败!${NC}"
|
echo -e "${RED}✗ 生成失败!${NC}"
|
||||||
echo "$GENERATE_RESPONSE" | jq '.'
|
ERR_MSG=$(echo "$GENERATE_RESPONSE" | jq -r '.message // .data // .')
|
||||||
|
echo -e " 原因: $ERR_MSG"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -14,11 +14,11 @@ export class AdminGuard implements CanActivate {
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest();
|
||||||
const user = request.user;
|
const user = request.user;
|
||||||
|
|
||||||
if (!user || !user.userId) {
|
if (!user || !user.id) {
|
||||||
throw new ForbiddenException('请先登录');
|
throw new ForbiddenException('请先登录');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAdmin = await this.adminService.isAdmin(user.userId);
|
const isAdmin = await this.adminService.isAdmin(user.id);
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
throw new ForbiddenException('需要管理员权限');
|
throw new ForbiddenException('需要管理员权限');
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { User } from '../user/entities/user.entity';
|
import { User } from '../../user/entities/user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminService {
|
export class AdminService {
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,12 @@ export class LicenseGuard implements CanActivate {
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest();
|
||||||
const user = request.user;
|
const user = request.user;
|
||||||
|
|
||||||
if (!user || !user.userId) {
|
if (!user || !user.id) {
|
||||||
throw new ForbiddenException('请先登录');
|
throw new ForbiddenException('请先登录');
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasValidLicense = await this.licenseService.verifyUserLicense(
|
const hasValidLicense = await this.licenseService.verifyUserLicense(
|
||||||
user.userId,
|
user.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasValidLicense) {
|
if (!hasValidLicense) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue