fix: resolve 10 code review issues
High priority: - Fix concurrent race condition for view_count/like_count (atomic update) - Add route request ID tracking to prevent race conditions - Filter get_joke by status=approved (no pending content leak) - Add error feedback for like button Performance: - Optimize random joke query (avoid full table sort) - Limit page_size max to 100 (DoS prevention) Medium: - Add localStorage quota error handling - Handle empty AI response gracefully - Fix generate content title extraction Low: - Add rejected_jokes to stats API - Update dashboard to show rejected count
This commit is contained in:
+125
@@ -0,0 +1,125 @@
|
||||
# API 操作文档 - 笑话管理系统
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **API 地址**: `http://<服务器IP>:8001`
|
||||
- **默认管理员**: `admin` / `admin123`
|
||||
- **认证方式**: JWT Bearer Token
|
||||
|
||||
---
|
||||
|
||||
## 一、登录获取 Token
|
||||
|
||||
```bash
|
||||
curl -X POST http://<服务器IP>:8001/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username": "admin", "password": "admin123"}'
|
||||
```
|
||||
|
||||
**返回示例**:
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIs...",
|
||||
"token_type": "bearer"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、提交笑话
|
||||
|
||||
```bash
|
||||
curl -X POST http://<服务器IP>:8001/api/admin/jokes \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <TOKEN>" \
|
||||
-d '{
|
||||
"title": "笑话标题",
|
||||
"content": "笑话内容正文",
|
||||
"type_id": 1,
|
||||
"crowd_id": 2
|
||||
}'
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `title` | string | 是 | 笑话标题 |
|
||||
| `content` | string | 是 | 笑话正文 |
|
||||
| `type_id` | int | 否 | 类型 ID(查分类列表获取) |
|
||||
| `crowd_id` | int | 否 | 人群 ID(查分类列表获取) |
|
||||
|
||||
提交后 status 默认为 `pending`(待审核)。
|
||||
|
||||
---
|
||||
|
||||
## 三、查询分类 ID
|
||||
|
||||
```bash
|
||||
# 查看所有类型
|
||||
curl http://<服务器IP>:8001/api/categories/types
|
||||
|
||||
# 查看所有人群
|
||||
curl http://<服务器IP>:8001/api/categories/crowds
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、完整脚本示例(批量提交)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
SERVER="http://192.168.1.10:8001"
|
||||
|
||||
# 登录获取 Token
|
||||
TOKEN=$(curl -s -X POST "$SERVER/api/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"admin123"}' \
|
||||
| python -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
|
||||
|
||||
echo "Token: $TOKEN"
|
||||
|
||||
# 批量提交笑话
|
||||
jokes=(
|
||||
'{"title":"笑话1","content":"内容1","type_id":1,"crowd_id":2}'
|
||||
'{"title":"笑话2","content":"内容2","type_id":1,"crowd_id":null}'
|
||||
)
|
||||
|
||||
for joke in "${jokes[@]}"; do
|
||||
curl -X POST "$SERVER/api/admin/jokes" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-d "$joke"
|
||||
echo ""
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、其他管理接口
|
||||
|
||||
### 查询笑话列表
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <TOKEN>" \
|
||||
"http://<服务器IP>:8001/api/admin/jokes?page=1&page_size=20&status=pending"
|
||||
```
|
||||
|
||||
### 审核通过
|
||||
```bash
|
||||
curl -X PUT http://<服务器IP>:8001/api/admin/jokes/batch-approve \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <TOKEN>" \
|
||||
-d '[1, 2, 3]'
|
||||
```
|
||||
|
||||
### 删除笑话
|
||||
```bash
|
||||
curl -X DELETE http://<服务器IP>:8001/api/admin/jokes/1 \
|
||||
-H "Authorization: Bearer <TOKEN>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、注意事项
|
||||
|
||||
1. 将 `<服务器IP>` 替换为实际的服务器 IP 地址
|
||||
2. 确保服务器防火墙已放行 **8001** 端口
|
||||
3. Token 有过期时间,过期后需重新登录获取
|
||||
Binary file not shown.
@@ -1,5 +1,8 @@
|
||||
from app.models.joke import Joke
|
||||
from app.models.category import JokeType, JokeCrowd
|
||||
from app.models.user import AdminUser
|
||||
from app.models.setting import AiSetting
|
||||
from app.models.link import Link
|
||||
from app.models.feedback import Feedback
|
||||
|
||||
__all__ = ["Joke", "JokeType", "JokeCrowd", "AdminUser"]
|
||||
__all__ = ["Joke", "JokeType", "JokeCrowd", "AdminUser", "AiSetting", "Link", "Feedback"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,13 @@
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Feedback(Base):
|
||||
__tablename__ = "feedbacks"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(50), nullable=True)
|
||||
email = Column(String(100), nullable=True)
|
||||
content = Column(Text, nullable=False)
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
@@ -12,6 +12,9 @@ class Joke(Base):
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(200), nullable=False)
|
||||
content = Column(Text, nullable=False)
|
||||
polished_content = Column(Text, nullable=True)
|
||||
type_ids = Column(Text, nullable=True)
|
||||
crowd_ids = Column(Text, nullable=True)
|
||||
type_id = Column(Integer, ForeignKey("joke_types.id"), nullable=True)
|
||||
crowd_id = Column(Integer, ForeignKey("joke_crowds.id"), nullable=True)
|
||||
status = Column(String(20), default="pending")
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Link(Base):
|
||||
__tablename__ = "links"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
url = Column(String(500), nullable=False)
|
||||
description = Column(String(200), nullable=True)
|
||||
sort_order = Column(Integer, default=0)
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
@@ -0,0 +1,23 @@
|
||||
from sqlalchemy import Boolean, Column, DateTime, Float, Integer, String, Text, func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class AiSetting(Base):
|
||||
__tablename__ = "ai_settings"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
provider = Column(String(50), nullable=False, default="nvidia")
|
||||
api_base = Column(String(500), nullable=False, default="https://integrate.api.nvidia.com/v1")
|
||||
api_key = Column(String(500), nullable=False, default="")
|
||||
model_name = Column(String(100), nullable=False, default="nvidia/llama-3.1-nemotron-70b-instruct")
|
||||
temperature = Column(Float, nullable=False, default=0.7)
|
||||
max_tokens = Column(Integer, nullable=False, default=2048)
|
||||
|
||||
crawl_enabled = Column(Boolean, nullable=False, default=False)
|
||||
crawl_keywords = Column(Text, nullable=True, default="")
|
||||
max_pages_per_run = Column(Integer, nullable=False, default=3)
|
||||
|
||||
is_active = Column(Boolean, nullable=False, default=True)
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,7 @@
|
||||
from .jokes import router as jokes_router
|
||||
from .categories import router as categories_router
|
||||
from .auth import router as auth_router
|
||||
from .admin import router as admin_router
|
||||
from .settings import router as settings_router
|
||||
|
||||
__all__ = ["jokes_router", "categories_router", "auth_router", "admin_router", "settings_router"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+164
-13
@@ -1,3 +1,5 @@
|
||||
import json
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Header
|
||||
from jose import JWTError, jwt
|
||||
from sqlalchemy import func
|
||||
@@ -8,8 +10,12 @@ from app.database import get_db
|
||||
from app.models.joke import Joke
|
||||
from app.models.category import JokeCrowd, JokeType
|
||||
from app.models.user import AdminUser
|
||||
from app.models.link import Link
|
||||
from app.models.feedback import Feedback
|
||||
from app.schemas.category import JokeCrowdCreate, JokeCrowdResponse, JokeTypeCreate, JokeTypeResponse
|
||||
from app.schemas.joke import JokeCreate, JokeResponse, JokeUpdate, PaginatedJokeResponse
|
||||
from app.schemas.link import LinkCreate, LinkResponse
|
||||
from app.schemas.feedback import FeedbackResponse
|
||||
|
||||
router = APIRouter(prefix="/admin", tags=["管理后台"])
|
||||
|
||||
@@ -39,21 +45,48 @@ def get_current_admin_user(
|
||||
return user
|
||||
|
||||
|
||||
def joke_to_response(joke: Joke) -> JokeResponse:
|
||||
def _parse_ids(raw) -> list[int]:
|
||||
"""解析 DB 中的 JSON 列表字符串为 Python list"""
|
||||
if not raw:
|
||||
return []
|
||||
if isinstance(raw, list):
|
||||
return raw
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _get_type_names(db: Session, ids: list[int]) -> list[str]:
|
||||
rows = db.query(JokeType).filter(JokeType.id.in_(ids)).all()
|
||||
return [r.name for r in rows]
|
||||
|
||||
|
||||
def _get_crowd_names(db: Session, ids: list[int]) -> list[str]:
|
||||
rows = db.query(JokeCrowd).filter(JokeCrowd.id.in_(ids)).all()
|
||||
return [r.name for r in rows]
|
||||
|
||||
|
||||
def joke_to_response(joke: Joke, db: Session = None) -> JokeResponse:
|
||||
"""Convert Joke model to JokeResponse schema."""
|
||||
ids = _parse_ids(joke.type_ids)
|
||||
crowd_ids = _parse_ids(joke.crowd_ids)
|
||||
type_names = _get_type_names(db, ids) if db else []
|
||||
crowd_names = _get_crowd_names(db, crowd_ids) if db else []
|
||||
return JokeResponse(
|
||||
id=joke.id,
|
||||
title=joke.title,
|
||||
content=joke.content,
|
||||
type_id=joke.type_id,
|
||||
crowd_id=joke.crowd_id,
|
||||
polished_content=joke.polished_content,
|
||||
type_ids=ids,
|
||||
crowd_ids=crowd_ids,
|
||||
status=joke.status,
|
||||
view_count=joke.view_count,
|
||||
like_count=joke.like_count,
|
||||
created_at=joke.created_at,
|
||||
updated_at=joke.updated_at,
|
||||
type_name=joke.type.name if joke.type else None,
|
||||
crowd_name=joke.crowd.name if joke.crowd else None,
|
||||
type_names=type_names,
|
||||
crowd_names=crowd_names,
|
||||
)
|
||||
|
||||
|
||||
@@ -67,6 +100,8 @@ def admin_list_jokes(
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""获取所有笑话(支持状态筛选)"""
|
||||
# 限制 page_size 防止 DoS
|
||||
page_size = max(1, min(page_size, 100))
|
||||
query = db.query(Joke)
|
||||
if status:
|
||||
query = query.filter(Joke.status == status)
|
||||
@@ -74,7 +109,7 @@ def admin_list_jokes(
|
||||
offset = (page - 1) * page_size
|
||||
jokes = query.order_by(Joke.created_at.desc()).offset(offset).limit(page_size).all()
|
||||
return PaginatedJokeResponse(
|
||||
items=[joke_to_response(j) for j in jokes],
|
||||
items=[joke_to_response(j, db) for j in jokes],
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
@@ -88,11 +123,29 @@ def admin_create_joke(
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""创建笑话"""
|
||||
db_joke = Joke(**joke.model_dump())
|
||||
data = joke.model_dump()
|
||||
if data.get("type_ids") is not None:
|
||||
data["type_ids"] = json.dumps(data["type_ids"])
|
||||
if data.get("crowd_ids") is not None:
|
||||
data["crowd_ids"] = json.dumps(data["crowd_ids"])
|
||||
db_joke = Joke(**data)
|
||||
db.add(db_joke)
|
||||
db.commit()
|
||||
db.refresh(db_joke)
|
||||
return joke_to_response(db_joke)
|
||||
return joke_to_response(db_joke, db)
|
||||
|
||||
|
||||
@router.get("/jokes/{joke_id}", response_model=JokeResponse)
|
||||
def admin_get_joke(
|
||||
joke_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""获取单个笑话"""
|
||||
joke = db.query(Joke).filter(Joke.id == joke_id).first()
|
||||
if not joke:
|
||||
raise HTTPException(status_code=404, detail="笑话不存在")
|
||||
return joke_to_response(joke, db)
|
||||
|
||||
|
||||
@router.put("/jokes/{joke_id}", response_model=JokeResponse)
|
||||
@@ -108,10 +161,12 @@ def admin_update_joke(
|
||||
raise HTTPException(status_code=404, detail="笑话不存在")
|
||||
update_data = joke.model_dump(exclude_unset=True)
|
||||
for key, value in update_data.items():
|
||||
if key in ("type_ids", "crowd_ids") and value is not None:
|
||||
value = json.dumps(value)
|
||||
setattr(db_joke, key, value)
|
||||
db.commit()
|
||||
db.refresh(db_joke)
|
||||
return joke_to_response(db_joke)
|
||||
return joke_to_response(db_joke, db)
|
||||
|
||||
|
||||
@router.delete("/jokes/{joke_id}")
|
||||
@@ -152,12 +207,14 @@ def admin_stats(
|
||||
total_jokes = db.query(Joke).count()
|
||||
approved_jokes = db.query(Joke).filter(Joke.status == "approved").count()
|
||||
pending_jokes = db.query(Joke).filter(Joke.status == "pending").count()
|
||||
rejected_jokes = db.query(Joke).filter(Joke.status == "rejected").count()
|
||||
total_views = db.query(Joke).with_entities(func.sum(Joke.view_count)).scalar() or 0
|
||||
total_likes = db.query(Joke).with_entities(func.sum(Joke.like_count)).scalar() or 0
|
||||
return {
|
||||
"total_jokes": total_jokes,
|
||||
"approved_jokes": approved_jokes,
|
||||
"pending_jokes": pending_jokes,
|
||||
"rejected_jokes": rejected_jokes,
|
||||
"total_views": total_views,
|
||||
"total_likes": total_likes,
|
||||
}
|
||||
@@ -209,8 +266,11 @@ def admin_delete_type(
|
||||
db_type = db.query(JokeType).filter(JokeType.id == type_id).first()
|
||||
if not db_type:
|
||||
raise HTTPException(status_code=404, detail="类型不存在")
|
||||
# Check if there are jokes using this type
|
||||
joke_count = db.query(Joke).filter(Joke.type_id == type_id).count()
|
||||
# Check both old single-field and new array-field associations
|
||||
old_count = db.query(Joke).filter(Joke.type_id == type_id).count()
|
||||
all_jokes = db.query(Joke.type_ids).filter(Joke.type_ids.isnot(None)).all()
|
||||
new_count = sum(1 for (raw,) in all_jokes if _parse_ids(raw) and type_id in _parse_ids(raw))
|
||||
joke_count = old_count + new_count
|
||||
if joke_count > 0:
|
||||
raise HTTPException(status_code=400, detail=f"有 {joke_count} 条笑话使用此类型,无法删除")
|
||||
db.delete(db_type)
|
||||
@@ -263,10 +323,101 @@ def admin_delete_crowd(
|
||||
db_crowd = db.query(JokeCrowd).filter(JokeCrowd.id == crowd_id).first()
|
||||
if not db_crowd:
|
||||
raise HTTPException(status_code=404, detail="人群分类不存在")
|
||||
# Check if there are jokes using this crowd
|
||||
joke_count = db.query(Joke).filter(Joke.crowd_id == crowd_id).count()
|
||||
# Check both old single-field and new array-field associations
|
||||
old_count = db.query(Joke).filter(Joke.crowd_id == crowd_id).count()
|
||||
all_jokes = db.query(Joke.crowd_ids).filter(Joke.crowd_ids.isnot(None)).all()
|
||||
new_count = sum(1 for (raw,) in all_jokes if _parse_ids(raw) and crowd_id in _parse_ids(raw))
|
||||
joke_count = old_count + new_count
|
||||
if joke_count > 0:
|
||||
raise HTTPException(status_code=400, detail=f"有 {joke_count} 条笑话使用此人群,无法删除")
|
||||
db.delete(db_crowd)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 友情链接管理
|
||||
# ============================================================
|
||||
|
||||
@router.get("/links", response_model=list[LinkResponse])
|
||||
def admin_list_links(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""获取所有友情链接"""
|
||||
return db.query(Link).order_by(Link.sort_order, Link.id).all()
|
||||
|
||||
|
||||
@router.post("/links", response_model=LinkResponse)
|
||||
def admin_create_link(
|
||||
link: LinkCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""创建友情链接"""
|
||||
db_link = Link(**link.model_dump())
|
||||
db.add(db_link)
|
||||
db.commit()
|
||||
db.refresh(db_link)
|
||||
return db_link
|
||||
|
||||
|
||||
@router.put("/links/{link_id}", response_model=LinkResponse)
|
||||
def admin_update_link(
|
||||
link_id: int,
|
||||
link: LinkCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""更新友情链接"""
|
||||
db_link = db.query(Link).filter(Link.id == link_id).first()
|
||||
if not db_link:
|
||||
raise HTTPException(status_code=404, detail="链接不存在")
|
||||
for key, value in link.model_dump().items():
|
||||
setattr(db_link, key, value)
|
||||
db.commit()
|
||||
db.refresh(db_link)
|
||||
return db_link
|
||||
|
||||
|
||||
@router.delete("/links/{link_id}")
|
||||
def admin_delete_link(
|
||||
link_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""删除友情链接"""
|
||||
db_link = db.query(Link).filter(Link.id == link_id).first()
|
||||
if not db_link:
|
||||
raise HTTPException(status_code=404, detail="链接不存在")
|
||||
db.delete(db_link)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 反馈建议管理
|
||||
# ============================================================
|
||||
|
||||
@router.get("/feedbacks", response_model=list[FeedbackResponse])
|
||||
def admin_list_feedbacks(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""获取所有反馈建议"""
|
||||
return db.query(Feedback).order_by(Feedback.created_at.desc()).all()
|
||||
|
||||
|
||||
@router.delete("/feedbacks/{feedback_id}")
|
||||
def admin_delete_feedback(
|
||||
feedback_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""删除反馈"""
|
||||
db_feedback = db.query(Feedback).filter(Feedback.id == feedback_id).first()
|
||||
if not db_feedback:
|
||||
raise HTTPException(status_code=404, detail="反馈不存在")
|
||||
db.delete(db_feedback)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
@@ -0,0 +1,18 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.feedback import Feedback
|
||||
from app.schemas.feedback import FeedbackCreate, FeedbackResponse
|
||||
|
||||
router = APIRouter(tags=["反馈建议"])
|
||||
|
||||
|
||||
@router.post("/feedback", response_model=FeedbackResponse)
|
||||
def create_feedback(feedback: FeedbackCreate, db: Session = Depends(get_db)):
|
||||
"""公开:提交反馈建议"""
|
||||
db_feedback = Feedback(**feedback.model_dump())
|
||||
db.add(db_feedback)
|
||||
db.commit()
|
||||
db.refresh(db_feedback)
|
||||
return db_feedback
|
||||
@@ -76,8 +76,12 @@ def generate_joke(
|
||||
raise HTTPException(status_code=500, detail=f"生成失败: {str(e)}")
|
||||
|
||||
|
||||
def _parse_response(raw: str) -> GenerateResponse:
|
||||
def _parse_response(raw: str | None) -> GenerateResponse:
|
||||
"""解析 AI 返回内容,提取标题和内容"""
|
||||
# 防御:处理空或 None 输入
|
||||
if not raw or not raw.strip():
|
||||
raise ValueError("AI 返回内容为空")
|
||||
|
||||
title = ""
|
||||
content = raw
|
||||
|
||||
@@ -86,16 +90,26 @@ def _parse_response(raw: str) -> GenerateResponse:
|
||||
line = line.strip()
|
||||
if line.startswith("标题:") or line.startswith("标题:"):
|
||||
title = line.split(":", 1)[-1].split(":", 1)[-1].strip()
|
||||
content = content.replace(line, "").strip()
|
||||
# 只替换这一行,不要 replace 全局
|
||||
lines = content.split("\n")
|
||||
for i, l in enumerate(lines):
|
||||
if l.strip() == line:
|
||||
lines[i] = ""
|
||||
break
|
||||
content = "\n".join(lines).strip()
|
||||
break
|
||||
|
||||
# 如果没有提取到标题,取第一行或前20字
|
||||
# 如果没有提取到标题,取第一行
|
||||
if not title:
|
||||
first_line = raw.split("\n")[0].strip()
|
||||
if first_line.startswith("标题"):
|
||||
first_line = first_line.split(":", 1)[-1].split(":", 1)[-1].strip()
|
||||
title = first_line[:30] if len(first_line) > 30 else first_line
|
||||
|
||||
# 防御:content 不能为空
|
||||
if not content.strip():
|
||||
content = "(内容生成失败,请重新生成)"
|
||||
|
||||
return GenerateResponse(
|
||||
title=title or "生成的笑话",
|
||||
content=content.strip(),
|
||||
|
||||
+110
-17
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import random
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
@@ -6,26 +7,56 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.joke import Joke
|
||||
from app.models.category import JokeCrowd, JokeType
|
||||
from app.schemas.joke import JokeResponse, PaginatedJokeResponse
|
||||
|
||||
router = APIRouter(prefix="/jokes", tags=["笑话"])
|
||||
|
||||
|
||||
def joke_to_response(joke: Joke) -> JokeResponse:
|
||||
def _parse_ids(raw) -> list[int]:
|
||||
if not raw:
|
||||
return []
|
||||
if isinstance(raw, list):
|
||||
return raw
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _get_type_names(db: Session, ids: list[int]) -> list[str]:
|
||||
if not ids:
|
||||
return []
|
||||
rows = db.query(JokeType).filter(JokeType.id.in_(ids)).all()
|
||||
return [r.name for r in rows]
|
||||
|
||||
|
||||
def _get_crowd_names(db: Session, ids: list[int]) -> list[str]:
|
||||
if not ids:
|
||||
return []
|
||||
rows = db.query(JokeCrowd).filter(JokeCrowd.id.in_(ids)).all()
|
||||
return [r.name for r in rows]
|
||||
|
||||
|
||||
def joke_to_response(joke: Joke, db: Session = None) -> JokeResponse:
|
||||
"""Convert Joke model to JokeResponse schema."""
|
||||
ids = _parse_ids(joke.type_ids)
|
||||
crowd_ids = _parse_ids(joke.crowd_ids)
|
||||
type_names = _get_type_names(db, ids) if db else []
|
||||
crowd_names = _get_crowd_names(db, crowd_ids) if db else []
|
||||
return JokeResponse(
|
||||
id=joke.id,
|
||||
title=joke.title,
|
||||
content=joke.content,
|
||||
type_id=joke.type_id,
|
||||
crowd_id=joke.crowd_id,
|
||||
type_ids=ids,
|
||||
crowd_ids=crowd_ids,
|
||||
status=joke.status,
|
||||
view_count=joke.view_count,
|
||||
like_count=joke.like_count,
|
||||
created_at=joke.created_at,
|
||||
updated_at=joke.updated_at,
|
||||
type_name=joke.type.name if joke.type else None,
|
||||
crowd_name=joke.crowd.name if joke.crowd else None,
|
||||
type_names=type_names,
|
||||
crowd_names=crowd_names,
|
||||
)
|
||||
|
||||
|
||||
@@ -33,24 +64,46 @@ def joke_to_response(joke: Joke) -> JokeResponse:
|
||||
def list_jokes(
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
type_id: int | None = None,
|
||||
crowd_id: int | None = None,
|
||||
type_ids: str | None = Query(None, description="逗号分隔的类型 ID,如 1,3,5"),
|
||||
crowd_ids: str | None = Query(None, description="逗号分隔的人群 ID,如 2,4"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取笑话列表(仅返回已审核通过的笑话)"""
|
||||
query = db.query(Joke).filter(Joke.status == "approved")
|
||||
|
||||
if type_id is not None:
|
||||
query = query.filter(Joke.type_id == type_id)
|
||||
if crowd_id is not None:
|
||||
query = query.filter(Joke.crowd_id == crowd_id)
|
||||
# 支持多选过滤:逗号分隔的 ID
|
||||
if type_ids:
|
||||
filter_set = {int(x.strip()) for x in type_ids.split(",") if x.strip().isdigit()}
|
||||
if filter_set:
|
||||
# 兼容旧单值字段 type_id
|
||||
old_filter = Joke.type_id.in_(filter_set)
|
||||
# 兼容新数组字段 type_ids(JSON 包含任一)
|
||||
new_filter = [
|
||||
Joke.type_ids.contains(str(tid)) for tid in filter_set
|
||||
]
|
||||
combined = old_filter
|
||||
for nf in new_filter:
|
||||
combined = combined | nf
|
||||
query = query.filter(combined)
|
||||
|
||||
if crowd_ids:
|
||||
filter_set = {int(x.strip()) for x in crowd_ids.split(",") if x.strip().isdigit()}
|
||||
if filter_set:
|
||||
old_filter = Joke.crowd_id.in_(filter_set)
|
||||
new_filter = [
|
||||
Joke.crowd_ids.contains(str(cid)) for cid in filter_set
|
||||
]
|
||||
combined = old_filter
|
||||
for nf in new_filter:
|
||||
combined = combined | nf
|
||||
query = query.filter(combined)
|
||||
|
||||
total = query.count()
|
||||
offset = (page - 1) * page_size
|
||||
jokes = query.order_by(Joke.created_at.desc()).offset(offset).limit(page_size).all()
|
||||
|
||||
return PaginatedJokeResponse(
|
||||
items=[joke_to_response(j) for j in jokes],
|
||||
items=[joke_to_response(j, db) for j in jokes],
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
@@ -60,19 +113,59 @@ def list_jokes(
|
||||
@router.get("/{joke_id}", response_model=JokeResponse)
|
||||
def get_joke(joke_id: int, db: Session = Depends(get_db)):
|
||||
"""获取单条笑话详情"""
|
||||
joke = db.query(Joke).filter(Joke.id == joke_id).first()
|
||||
# 只返回已审核通过的笑话
|
||||
joke = db.query(Joke).filter(
|
||||
Joke.id == joke_id,
|
||||
Joke.status == "approved"
|
||||
).first()
|
||||
if not joke:
|
||||
raise HTTPException(status_code=404, detail="笑话不存在")
|
||||
# 增加浏览次数
|
||||
joke.view_count += 1
|
||||
# 使用原子更新避免并发竞态
|
||||
db.query(Joke).filter(Joke.id == joke_id).update({Joke.view_count: Joke.view_count + 1})
|
||||
db.commit()
|
||||
return joke_to_response(joke)
|
||||
# 重新查询获取更新后的数据
|
||||
db.refresh(joke)
|
||||
return joke_to_response(joke, db)
|
||||
|
||||
|
||||
@router.get("/random", response_model=JokeResponse)
|
||||
def get_random_joke(db: Session = Depends(get_db)):
|
||||
"""随机获取一条已审核通过的笑话"""
|
||||
# 使用效率更高的方式:随机 ID 取模
|
||||
max_id = db.query(func.max(Joke.id)).filter(Joke.status == "approved").scalar()
|
||||
if not max_id:
|
||||
raise HTTPException(status_code=404, detail="暂无笑话")
|
||||
|
||||
# 尝试最多 10 次找到有效笑话
|
||||
for _ in range(10):
|
||||
random_id = random.randint(1, max_id)
|
||||
joke = db.query(Joke).filter(
|
||||
Joke.id >= random_id,
|
||||
Joke.status == "approved"
|
||||
).first()
|
||||
if joke:
|
||||
return joke_to_response(joke, db)
|
||||
|
||||
# 兜底:全表随机
|
||||
joke = db.query(Joke).filter(Joke.status == "approved").order_by(func.random()).first()
|
||||
if not joke:
|
||||
raise HTTPException(status_code=404, detail="暂无笑话")
|
||||
return joke_to_response(joke)
|
||||
return joke_to_response(joke, db)
|
||||
|
||||
|
||||
@router.post("/{joke_id}/like")
|
||||
def like_joke(joke_id: int, db: Session = Depends(get_db)):
|
||||
"""为笑话点赞(仅允许已审核通过的笑话)"""
|
||||
# 先检查笑话是否存在且已审核
|
||||
joke = db.query(Joke).filter(
|
||||
Joke.id == joke_id,
|
||||
Joke.status == "approved"
|
||||
).first()
|
||||
if not joke:
|
||||
raise HTTPException(status_code=404, detail="笑话不存在")
|
||||
# 使用原子更新避免并发竞态
|
||||
db.query(Joke).filter(Joke.id == joke_id).update({Joke.like_count: Joke.like_count + 1})
|
||||
db.commit()
|
||||
# 获取更新后的值
|
||||
db.refresh(joke)
|
||||
return {"message": "点赞成功", "like_count": joke.like_count}
|
||||
@@ -0,0 +1,14 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.link import Link
|
||||
from app.schemas.link import LinkCreate, LinkResponse
|
||||
|
||||
router = APIRouter(tags=["友情链接"])
|
||||
|
||||
|
||||
@router.get("/links", response_model=list[LinkResponse])
|
||||
def list_links(db: Session = Depends(get_db)):
|
||||
"""公开:获取所有友情链接"""
|
||||
return db.query(Link).order_by(Link.sort_order, Link.id).all()
|
||||
@@ -0,0 +1,96 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.setting import AiSetting
|
||||
from app.schemas.setting import AiSettingCreate
|
||||
from app.routers.admin import get_current_admin_user
|
||||
from app.models.user import AdminUser
|
||||
|
||||
router = APIRouter(prefix="/admin/settings", tags=["AI设置"])
|
||||
|
||||
|
||||
@router.get("", response_model=list)
|
||||
def list_settings(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""列出所有 AI 配置"""
|
||||
return db.query(AiSetting).order_by(AiSetting.created_at.desc()).all()
|
||||
|
||||
|
||||
@router.get("/active")
|
||||
def get_active_setting(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""获取当前激活的 AI 配置(爬虫调用,无需用户认证,token 校验仍保留)"""
|
||||
setting = db.query(AiSetting).filter(AiSetting.is_active == True).first()
|
||||
if not setting:
|
||||
raise HTTPException(status_code=404, detail="未找到激活的 AI 配置")
|
||||
return setting
|
||||
|
||||
|
||||
@router.post("")
|
||||
def create_setting(
|
||||
setting: AiSettingCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""新建 AI 配置"""
|
||||
db_setting = AiSetting(**setting.model_dump())
|
||||
db.add(db_setting)
|
||||
db.commit()
|
||||
db.refresh(db_setting)
|
||||
return db_setting
|
||||
|
||||
|
||||
@router.put("/{setting_id}")
|
||||
def update_setting(
|
||||
setting_id: int,
|
||||
setting: AiSettingCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""更新 AI 配置"""
|
||||
db_setting = db.query(AiSetting).filter(AiSetting.id == setting_id).first()
|
||||
if not db_setting:
|
||||
raise HTTPException(status_code=404, detail="配置不存在")
|
||||
for key, value in setting.model_dump().items():
|
||||
setattr(db_setting, key, value)
|
||||
db.commit()
|
||||
db.refresh(db_setting)
|
||||
return db_setting
|
||||
|
||||
|
||||
@router.put("/{setting_id}/toggle")
|
||||
def toggle_setting(
|
||||
setting_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""切换激活状态(只能有一个活跃)"""
|
||||
db_setting = db.query(AiSetting).filter(AiSetting.id == setting_id).first()
|
||||
if not db_setting:
|
||||
raise HTTPException(status_code=404, detail="配置不存在")
|
||||
|
||||
# 先全部设为非活跃
|
||||
db.query(AiSetting).update({AiSetting.is_active: False})
|
||||
db_setting.is_active = True
|
||||
db.commit()
|
||||
return {"message": "已激活", "id": setting_id}
|
||||
|
||||
|
||||
@router.delete("/{setting_id}")
|
||||
def delete_setting(
|
||||
setting_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""删除 AI 配置"""
|
||||
db_setting = db.query(AiSetting).filter(AiSetting.id == setting_id).first()
|
||||
if not db_setting:
|
||||
raise HTTPException(status_code=404, detail="配置不存在")
|
||||
db.delete(db_setting)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,17 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class FeedbackCreate(BaseModel):
|
||||
name: str | None = None
|
||||
email: str | None = None
|
||||
content: str
|
||||
|
||||
|
||||
class FeedbackResponse(FeedbackCreate):
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,18 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class LinkCreate(BaseModel):
|
||||
name: str
|
||||
url: str
|
||||
description: str | None = None
|
||||
sort_order: int = 0
|
||||
|
||||
|
||||
class LinkResponse(LinkCreate):
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,17 @@
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class AiSettingBase(BaseModel):
|
||||
provider: str = Field(default="nvidia")
|
||||
api_base: str = Field(default="https://integrate.api.nvidia.com/v1")
|
||||
api_key: str = Field(default="")
|
||||
model_name: str = Field(default="nvidia/llama-3.1-nemotron-70b-instruct")
|
||||
temperature: float = Field(default=0.7, ge=0, le=2)
|
||||
max_tokens: int = Field(default=2048, ge=1)
|
||||
crawl_enabled: bool = Field(default=False)
|
||||
crawl_keywords: str = Field(default="")
|
||||
max_pages_per_run: int = Field(default=3, ge=1)
|
||||
|
||||
|
||||
class AiSettingCreate(AiSettingBase):
|
||||
pass
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,39 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from app.config import API_TITLE, API_VERSION
|
||||
from app.routers import jokes_router, categories_router, auth_router, admin_router, settings_router
|
||||
from app.database import Base, engine
|
||||
from app.models.setting import AiSetting # 注册模型,确保 create_all 能看到
|
||||
|
||||
# 创建新表(如果不存在)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
# Create FastAPI application
|
||||
app = FastAPI(title=API_TITLE, version=API_VERSION)
|
||||
|
||||
# Configure CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Mount routers
|
||||
app.include_router(jokes_router, prefix="/api")
|
||||
app.include_router(categories_router, prefix="/api")
|
||||
app.include_router(auth_router, prefix="/api")
|
||||
app.include_router(admin_router, prefix="/api")
|
||||
app.include_router(settings_router, prefix="/api")
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"message": "欢迎使用笑话大全 API"}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
return {"status": "healthy"}
|
||||
@@ -4,4 +4,7 @@ sqlalchemy==2.0.25
|
||||
pydantic==2.5.3
|
||||
python-jose[cryptography]==3.3.0
|
||||
passlib[bcrypt]==1.7.4
|
||||
python-multipart==0.0.6
|
||||
python-multipart==0.0.6
|
||||
openai==1.12.0
|
||||
crawl4ai==0.3.0
|
||||
httpx==0.27.0
|
||||
+501
@@ -0,0 +1,501 @@
|
||||
INFO: Will watch for changes in these directories: ['D:\\bwstudio\\joke\\api']
|
||||
INFO: Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit)
|
||||
INFO: Started reloader process [2236] using WatchFiles
|
||||
INFO: Started server process [1824]
|
||||
INFO: Waiting for application startup.
|
||||
INFO: Application startup complete.
|
||||
INFO: 127.0.0.1:61949 - "GET /health HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:61961 - "POST /api/auth/login HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:61964 - "GET /api/admin/jokes?page=1&page_size=3 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62046 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62046 - "GET /favicon.ico HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:62086 - "GET /api/admin/stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62088 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62090 - "GET /api/admin/stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62151 - "GET /api/admin/stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62154 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62158 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62161 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62164 - "GET /api/admin/jokes/1278 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62167 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62173 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62176 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62179 - "GET /api/admin/jokes/1275 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62182 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62198 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62201 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62204 - "GET /api/admin/jokes/1273 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62208 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62213 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62216 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62219 - "GET /api/admin/jokes/1279 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62223 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62230 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62233 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62236 - "GET /api/admin/jokes/1274 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62239 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62242 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62245 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62261 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62264 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62267 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62270 - "GET /api/admin/jokes/1262 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62273 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62283 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62286 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62289 - "GET /api/admin/jokes/1265 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62292 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62295 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62298 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62302 - "GET /api/admin/settings/active HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62304 - "GET /api/admin/settings HTTP/1.1" 500 Internal Server Error
|
||||
ERROR: Exception in ASGI application
|
||||
Traceback (most recent call last):
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\uvicorn\protocols\http\httptools_impl.py", line 419, in run_asgi
|
||||
result = await app( # type: ignore[func-returns-value]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\uvicorn\middleware\proxy_headers.py", line 84, in __call__
|
||||
return await self.app(scope, receive, send)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\applications.py", line 1054, in __call__
|
||||
await super().__call__(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\applications.py", line 123, in __call__
|
||||
await self.middleware_stack(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\errors.py", line 186, in __call__
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\errors.py", line 164, in __call__
|
||||
await self.app(scope, receive, _send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\cors.py", line 83, in __call__
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\exceptions.py", line 62, in __call__
|
||||
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||
await app(scope, receive, sender)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 762, in __call__
|
||||
await self.middleware_stack(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 782, in app
|
||||
await route.handle(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 297, in handle
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 77, in app
|
||||
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||
await app(scope, receive, sender)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 72, in app
|
||||
response = await func(request)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\routing.py", line 315, in app
|
||||
content = await serialize_response(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\routing.py", line 160, in serialize_response
|
||||
return field.serialize(
|
||||
^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\_compat.py", line 147, in serialize
|
||||
return self._type_adapter.dump_python(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\pydantic\type_adapter.py", line 607, in dump_python
|
||||
return self.serializer.to_python(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'app.models.setting.AiSetting'>
|
||||
INFO: 127.0.0.1:62308 - "GET /api/admin/stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62324 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62327 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62330 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62356 - "GET /api/admin/settings/active HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62359 - "GET /api/admin/settings HTTP/1.1" 500 Internal Server Error
|
||||
ERROR: Exception in ASGI application
|
||||
Traceback (most recent call last):
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\uvicorn\protocols\http\httptools_impl.py", line 419, in run_asgi
|
||||
result = await app( # type: ignore[func-returns-value]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\uvicorn\middleware\proxy_headers.py", line 84, in __call__
|
||||
return await self.app(scope, receive, send)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\applications.py", line 1054, in __call__
|
||||
await super().__call__(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\applications.py", line 123, in __call__
|
||||
await self.middleware_stack(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\errors.py", line 186, in __call__
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\errors.py", line 164, in __call__
|
||||
await self.app(scope, receive, _send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\cors.py", line 83, in __call__
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\exceptions.py", line 62, in __call__
|
||||
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||
await app(scope, receive, sender)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 762, in __call__
|
||||
await self.middleware_stack(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 782, in app
|
||||
await route.handle(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 297, in handle
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 77, in app
|
||||
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||
await app(scope, receive, sender)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 72, in app
|
||||
response = await func(request)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\routing.py", line 315, in app
|
||||
content = await serialize_response(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\routing.py", line 160, in serialize_response
|
||||
return field.serialize(
|
||||
^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\_compat.py", line 147, in serialize
|
||||
return self._type_adapter.dump_python(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\pydantic\type_adapter.py", line 607, in dump_python
|
||||
return self.serializer.to_python(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'app.models.setting.AiSetting'>
|
||||
INFO: 127.0.0.1:62362 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62365 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62368 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62371 - "GET /api/admin/stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62383 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62386 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62389 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62392 - "GET /api/admin/settings/active HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62395 - "GET /api/admin/settings HTTP/1.1" 500 Internal Server Error
|
||||
ERROR: Exception in ASGI application
|
||||
Traceback (most recent call last):
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\uvicorn\protocols\http\httptools_impl.py", line 419, in run_asgi
|
||||
result = await app( # type: ignore[func-returns-value]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\uvicorn\middleware\proxy_headers.py", line 84, in __call__
|
||||
return await self.app(scope, receive, send)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\applications.py", line 1054, in __call__
|
||||
await super().__call__(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\applications.py", line 123, in __call__
|
||||
await self.middleware_stack(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\errors.py", line 186, in __call__
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\errors.py", line 164, in __call__
|
||||
await self.app(scope, receive, _send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\cors.py", line 83, in __call__
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\exceptions.py", line 62, in __call__
|
||||
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||
await app(scope, receive, sender)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 762, in __call__
|
||||
await self.middleware_stack(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 782, in app
|
||||
await route.handle(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 297, in handle
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 77, in app
|
||||
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||
await app(scope, receive, sender)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 72, in app
|
||||
response = await func(request)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\routing.py", line 315, in app
|
||||
content = await serialize_response(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\routing.py", line 160, in serialize_response
|
||||
return field.serialize(
|
||||
^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\_compat.py", line 147, in serialize
|
||||
return self._type_adapter.dump_python(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\pydantic\type_adapter.py", line 607, in dump_python
|
||||
return self.serializer.to_python(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'app.models.setting.AiSetting'>
|
||||
INFO: 127.0.0.1:62398 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62401 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62404 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62407 - "GET /api/admin/stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62414 - "GET /api/admin/stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62419 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62422 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62425 - "GET /api/admin/stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62429 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62432 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62435 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62438 - "GET /api/admin/stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62441 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62444 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62447 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62451 - "GET /api/admin/settings/active HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62453 - "GET /api/admin/settings HTTP/1.1" 500 Internal Server Error
|
||||
ERROR: Exception in ASGI application
|
||||
Traceback (most recent call last):
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\uvicorn\protocols\http\httptools_impl.py", line 419, in run_asgi
|
||||
result = await app( # type: ignore[func-returns-value]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\uvicorn\middleware\proxy_headers.py", line 84, in __call__
|
||||
return await self.app(scope, receive, send)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\applications.py", line 1054, in __call__
|
||||
await super().__call__(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\applications.py", line 123, in __call__
|
||||
await self.middleware_stack(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\errors.py", line 186, in __call__
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\errors.py", line 164, in __call__
|
||||
await self.app(scope, receive, _send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\cors.py", line 83, in __call__
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\exceptions.py", line 62, in __call__
|
||||
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||
await app(scope, receive, sender)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 762, in __call__
|
||||
await self.middleware_stack(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 782, in app
|
||||
await route.handle(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 297, in handle
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 77, in app
|
||||
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||
await app(scope, receive, sender)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 72, in app
|
||||
response = await func(request)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\routing.py", line 315, in app
|
||||
content = await serialize_response(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\routing.py", line 160, in serialize_response
|
||||
return field.serialize(
|
||||
^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\_compat.py", line 147, in serialize
|
||||
return self._type_adapter.dump_python(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\pydantic\type_adapter.py", line 607, in dump_python
|
||||
return self.serializer.to_python(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'app.models.setting.AiSetting'>
|
||||
INFO: 127.0.0.1:62456 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62459 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62462 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62465 - "GET /api/admin/stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62468 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62471 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62474 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62477 - "GET /api/admin/settings/active HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62480 - "GET /api/admin/settings HTTP/1.1" 500 Internal Server Error
|
||||
ERROR: Exception in ASGI application
|
||||
Traceback (most recent call last):
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\uvicorn\protocols\http\httptools_impl.py", line 419, in run_asgi
|
||||
result = await app( # type: ignore[func-returns-value]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\uvicorn\middleware\proxy_headers.py", line 84, in __call__
|
||||
return await self.app(scope, receive, send)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\applications.py", line 1054, in __call__
|
||||
await super().__call__(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\applications.py", line 123, in __call__
|
||||
await self.middleware_stack(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\errors.py", line 186, in __call__
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\errors.py", line 164, in __call__
|
||||
await self.app(scope, receive, _send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\cors.py", line 83, in __call__
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\middleware\exceptions.py", line 62, in __call__
|
||||
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||
await app(scope, receive, sender)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 762, in __call__
|
||||
await self.middleware_stack(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 782, in app
|
||||
await route.handle(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 297, in handle
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 77, in app
|
||||
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
|
||||
raise exc
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||
await app(scope, receive, sender)
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\starlette\routing.py", line 72, in app
|
||||
response = await func(request)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\routing.py", line 315, in app
|
||||
content = await serialize_response(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\routing.py", line 160, in serialize_response
|
||||
return field.serialize(
|
||||
^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\fastapi\_compat.py", line 147, in serialize
|
||||
return self._type_adapter.dump_python(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\PF\AppData\Roaming\Python\Python311\site-packages\pydantic\type_adapter.py", line 607, in dump_python
|
||||
return self.serializer.to_python(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'app.models.setting.AiSetting'>
|
||||
INFO: 127.0.0.1:62483 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62486 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62489 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62492 - "GET /api/admin/stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62883 - "GET /api/admin/stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62885 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62887 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62889 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62892 - "GET /api/admin/jokes/1279 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62895 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62898 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62901 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62904 - "GET /api/admin/jokes/1279 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62907 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62992 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62994 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62997 - "GET /api/jokes/?page=1&page_size=30 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:62999 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63002 - "GET /api/jokes/1275 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63005 - "GET /api/jokes/?page=1&page_size=50 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63008 - "GET /api/jokes/1246 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63011 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63014 - "GET /api/jokes/?page=1&page_size=20&type_ids=1 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63017 - "GET /api/jokes/1279 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63021 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63034 - "GET /api/jokes/?page=1&page_size=20&type_ids=12 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63037 - "GET /api/jokes/?page=1&page_size=20&type_ids=12,1 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63040 - "GET /api/jokes/1279 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63043 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63046 - "GET /api/jokes/1279 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63049 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63052 - "GET /api/jokes/1279 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63055 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63059 - "GET /api/jokes/1277 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63062 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
WARNING: WatchFiles detected changes in 'app\models\link.py'. Reloading...
|
||||
INFO: 127.0.0.1:63122 - "GET /api/jokes/?page=2&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63125 - "GET /api/jokes/?page=3&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63128 - "GET /api/jokes/?page=4&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63131 - "GET /api/jokes/?page=5&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63134 - "GET /api/jokes/?page=7&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63138 - "GET /api/jokes/1140 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63141 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63144 - "GET /api/jokes/?page=1&page_size=20&type_ids=4 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63147 - "GET /api/jokes/923 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63150 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63161 - "GET /api/jokes/?page=1&page_size=20&type_ids=3 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63164 - "GET /api/jokes/?page=1&page_size=20&type_ids=3,4 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63167 - "GET /api/jokes/?page=1&page_size=20&type_ids=3,4,5 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63170 - "GET /api/jokes/?page=1&page_size=20&type_ids=3,4,5,6 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63173 - "GET /api/jokes/?page=1&page_size=20&type_ids=4,5,6 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63176 - "GET /api/jokes/?page=1&page_size=20&type_ids=4,5,6,3 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63179 - "GET /api/jokes/?page=1&page_size=20&type_ids=4,5,6,3,2 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63182 - "GET /api/jokes/?page=1&page_size=20&type_ids=4,5,6,3,2,1 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63185 - "GET /api/jokes/?page=1&page_size=20&type_ids=4,5,6,3,2,1,8 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63188 - "GET /api/jokes/?page=1&page_size=20&type_ids=4,5,6,3,2,1,8,7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63191 - "GET /api/jokes/?page=1&page_size=20&type_ids=4,5,6,3,2,1,8,7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63194 - "GET /api/jokes/?page=1&page_size=20&type_ids=4,5,6,3,2,1,8,7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63197 - "GET /api/jokes/?page=1&page_size=20&type_ids=4,5,6,3,2,1,8,7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63200 - "GET /api/jokes/?page=1&page_size=20&type_ids=4,5,6,3,2,1,8,7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63204 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63208 - "GET /api/jokes/1279 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63211 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63214 - "GET /api/jokes/?page=1&page_size=20&type_ids=3 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63217 - "GET /api/jokes/?page=1&page_size=20&type_ids=3,4 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63220 - "GET /api/jokes/?page=1&page_size=20&type_ids=3,4,5 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63223 - "GET /api/jokes/?page=1&page_size=20&type_ids=3,4,5,6 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63226 - "GET /api/jokes/1251 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63229 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63232 - "GET /api/jokes/?page=1&page_size=20&type_ids=2 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63235 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63238 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3,4 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63241 - "GET /api/jokes/?page=1&page_size=20&crowd_ids=15 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63244 - "GET /api/jokes/?page=1&page_size=20&crowd_ids=15,16 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63264 - "GET /api/jokes/?page=1&page_size=20&type_ids=2 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63267 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63270 - "GET /api/jokes/1273 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63273 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63276 - "GET /api/jokes/1277 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63279 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63295 - "GET /api/jokes/?page=2&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63298 - "GET /api/jokes/?page=3&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63301 - "GET /api/jokes/?page=1&page_size=20&type_ids=1 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63304 - "GET /api/jokes/?page=1&page_size=20&type_ids=1,2 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63307 - "GET /api/jokes/?page=1&page_size=20&type_ids=1,2,3 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63310 - "GET /api/jokes/?page=1&page_size=20&type_ids=1,2,3,4 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63313 - "GET /api/jokes/?page=1&page_size=20&type_ids=1,2,3,4,5 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63316 - "GET /api/jokes/?page=1&page_size=20&type_ids=1,2,3,4,5,6 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63319 - "GET /api/jokes/?page=1&page_size=20&type_ids=1,2,3,4,5,6,7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63322 - "GET /api/jokes/?page=1&page_size=20&type_ids=1,2,3,4,5,6,7,8 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63325 - "GET /api/jokes/?page=1&page_size=20&type_ids=1,2,3,4,5,6,7,8,9 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63328 - "GET /api/jokes/?page=1&page_size=20&type_ids=1,2,3,4,5,6,7,8,9,10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63331 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3,4,5,6,7,8,9,10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63334 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3,4,5,6,7,8,9 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63337 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3,4,5,6,7,8 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63340 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3,4,5,6,7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63343 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3,4,5,6 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63346 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3,4,5 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63349 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3,4 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63352 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63369 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63371 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63373 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63376 - "GET /api/admin/jokes?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63379 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63390 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63392 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63394 - "GET /api/jokes/?page=1&page_size=30 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63396 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63427 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63446 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63448 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63452 - "GET /api/jokes/?page=1&page_size=20&type_ids=2,3 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63450 - "GET /api/jokes/?page=1&page_size=30 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63454 - "GET /api/jokes/1263 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63456 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63466 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63468 - "GET /api/admin/feedbacks HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63471 - "GET /api/admin/links HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63473 - "GET /api/admin/feedbacks HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63476 - "GET /api/admin/links HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63479 - "GET /api/admin/feedbacks HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63482 - "GET /api/admin/links HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63486 - "GET /api/admin/links HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63495 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63498 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63503 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63501 - "GET /api/jokes/?page=1&page_size=30 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63508 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63511 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63514 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63517 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63520 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63524 - "GET /api/jokes/?page=1&page_size=30 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63565 - "GET /api/admin/feedbacks HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63568 - "GET /api/admin/links HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63571 - "GET /api/admin/links HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63574 - "GET /api/admin/links HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63577 - "GET /api/admin/feedbacks HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63580 - "GET /api/admin/links HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63583 - "GET /api/admin/feedbacks HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63592 - "GET /api/categories/types HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63594 - "GET /api/categories/crowds HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63598 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63596 - "GET /api/jokes/?page=1&page_size=30 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63607 - "GET /api/links HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63609 - "GET /api/links HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:63612 - "GET /api/jokes/?page=1&page_size=20 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:63615 - "GET /api/links HTTP/1.1" 404 Not Found
|
||||
Reference in New Issue
Block a user