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:
bwstudio
2026-06-02 20:35:08 +08:00
parent 0b43973236
commit ceed63fcb0
144 changed files with 191660 additions and 270 deletions
+125
View File
@@ -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.
+4 -1
View File
@@ -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.
+13
View File
@@ -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())
+3
View File
@@ -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")
+14
View File
@@ -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())
+23
View File
@@ -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())
+7
View File
@@ -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
View File
@@ -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": "删除成功"}
+18
View File
@@ -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
+17 -3
View File
@@ -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
View File
@@ -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}
+14
View File
@@ -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()
+96
View File
@@ -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.
+17
View File
@@ -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
+18
View File
@@ -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
+17
View File
@@ -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
View File
Binary file not shown.
+39
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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