chore: 清理 Docker 相关文件并优化前端布局

- 删除 Docker 相关文件 (docker-compose, Dockerfile, nginx.conf, init.sql 等)
- 优化 platforms.html 卡片布局和响应式样式
- 优化 users.html 格式和移动端卡片设计
- 优化 admin.html 页面结构和表格布局
- 修复各页面 min-height 和溢出问题
- 更新导航组件样式
This commit is contained in:
lt
2026-05-09 19:41:59 +08:00
parent e77a1aa4d9
commit 71cb4c35a8
80 changed files with 142574 additions and 3900 deletions
-49
View File
@@ -1,49 +0,0 @@
# 宇之然内容创作平台 - 后端Docker镜像
FROM python:3.10-slim as builder
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# 复制requirements文件
COPY requirements.txt .
# 安装Python依赖(带缓存优化)
RUN pip install --user --no-cache-dir -r requirements.txt
# 生产阶段
FROM python:3.10-slim
WORKDIR /app
# 从builder阶段复制已安装的依赖
COPY --from=builder /root/.local /root/.local
COPY . .
# 确保PATH包含用户本地bin目录
ENV PATH=/root/.local/bin:$PATH
# 创建非root用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
RUN chown -R appuser:appuser /app
USER appuser
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8001/health || exit 1
EXPOSE 8001
# 运行应用
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]
# 标签信息
LABEL maintainer="宇之然团队"
LABEL version="1.0.0"
LABEL description="企业级内容创作管理系统"
+182
View File
@@ -0,0 +1,182 @@
# 宇之然内容创作平台 - 管理员API
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from core.security import get_current_admin_user
from app.database import get_db
from app.models import User, AuditLog
router = APIRouter()
@router.get("/users", response_model=List[dict])
async def get_users(
current_user: User = Depends(get_current_admin_user),
db: Session = Depends(get_db)
):
"""获取用户列表(管理员功能)"""
users = db.query(User).all()
result = []
for user in users:
result.append({
"id": user.id,
"username": user.username,
"role": user.role,
"created_at": user.created_at.isoformat() if user.created_at else None,
"last_login": user.last_login.isoformat() if user.last_login else None
})
return result
@router.post("/users", response_model=dict)
async def create_user(
username: str,
password: str,
role: str = "user",
current_user: User = Depends(get_current_admin_user),
db: Session = Depends(get_db)
):
"""创建新用户(管理员功能)"""
# 检查用户名是否已存在
existing_user = db.query(User).filter(User.username == username).first()
if existing_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="用户名已存在"
)
# 验证角色
if role not in ["admin", "user"]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="角色必须是 admin 或 user"
)
# 导入密码哈希函数
from core.security import get_password_hash
# 创建新用户
new_user = User(
username=username,
password_hash=get_password_hash(password),
role=role
)
db.add(new_user)
db.commit()
db.refresh(new_user)
# 记录审计日志
from core.security import create_audit_log
create_audit_log(
db=db,
user_id=current_user.id,
action="create_user",
resource_type="user",
resource_id=new_user.id,
details=f"角色: {role}"
)
return {
"id": new_user.id,
"username": new_user.username,
"role": new_user.role,
"created_at": new_user.created_at.isoformat() if new_user.created_at else None
}
@router.put("/users/{user_id}", response_model=dict)
async def update_user(
user_id: int,
username: str = None,
role: str = None,
current_user: User = Depends(get_current_admin_user),
db: Session = Depends(get_db)
):
"""更新用户信息(管理员功能)"""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
updates = {}
if username is not None:
# 检查新用户名是否已被使用(除了当前用户)
existing = db.query(User).filter(
User.username == username,
User.id != user_id
).first()
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="用户名已被使用"
)
user.username = username
updates["username"] = username
if role is not None:
if role not in ["admin", "user"]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="角色必须是 admin 或 user"
)
user.role = role
updates["role"] = role
if updates:
db.commit()
# 记录审计日志
from core.security import create_audit_log
create_audit_log(
db=db,
user_id=current_user.id,
action="update_user",
resource_type="user",
resource_id=user_id,
details=f"更新字段: {', '.join(updates.keys())}"
)
return {
"id": user.id,
"username": user.username,
"role": user.role,
"updated_at": datetime.utcnow().isoformat()
}
@router.delete("/users/{user_id}")
async def delete_user(
user_id: int,
current_user: User = Depends(get_current_admin_user),
db: Session = Depends(get_db)
):
"""删除用户(管理员功能)"""
# 不能删除自己
if user_id == current_user.id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="不能删除自己的账户"
)
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
db.delete(user)
db.commit()
# 记录审计日志
from core.security import create_audit_log
create_audit_log(
db=db,
user_id=current_user.id,
action="delete_user",
resource_type="user",
resource_id=user_id,
details="用户账户已删除"
)
return {"message": "用户已成功删除"}
+189
View File
@@ -0,0 +1,189 @@
# 宇之然内容创作平台 - 选题管理API
from pydantic import BaseModel
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from typing import List, Optional
import json
from core.security import get_current_admin_user, create_audit_log
from app.database import get_db
from app.models import Topic, User
router = APIRouter()
class TopicCreateRequest(BaseModel):
title: str
field: Optional[str] = None
priority_score: int = 0
class TopicUpdateRequest(BaseModel):
title: Optional[str] = None
field: Optional[str] = None
priority_score: Optional[int] = None
status: Optional[str] = None
@router.get("/", response_model=List[dict])
async def get_topics(
status: Optional[str] = Query(None),
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
db: Session = Depends(get_db)
):
"""获取选题列表"""
query = db.query(Topic)
# 状态筛选
if status:
query = query.filter(Topic.status == status)
# 分页
offset = (page - 1) * size
topics = query.offset(offset).limit(size).all()
# 转换为字典格式
result = []
for topic in topics:
result.append({
"id": topic.id,
"title": topic.title,
"field": topic.field,
"priority_score": topic.priority_score,
"status": topic.status,
"compliance_score": topic.compliance_score,
"created_at": topic.created_at.isoformat() if topic.created_at else None,
"updated_at": topic.updated_at.isoformat() if topic.updated_at else None,
"generated_at": topic.generated_at.isoformat() if topic.generated_at else None,
"published_at": topic.published_at.isoformat() if topic.published_at else None,
"platform_urls": topic.platform_urls
})
return result
@router.post("/", response_model=dict)
async def create_topic(
topic_data: TopicCreateRequest,
current_user: User = Depends(get_current_admin_user),
db: Session = Depends(get_db)
):
"""创建新选题(管理员功能)"""
new_topic = Topic(
title=topic_data.title,
field=topic_data.field,
priority_score=topic_data.priority_score,
status="待处理"
)
db.add(new_topic)
db.commit()
db.refresh(new_topic)
# 记录审计日志
create_audit_log(
db=db,
user_id=current_user.id,
action="create_topic",
resource_type="topic",
resource_id=new_topic.id,
details=f"标题: {topic_data.title}"
)
return {
"id": new_topic.id,
"title": new_topic.title,
"status": new_topic.status,
"created_at": new_topic.created_at.isoformat() if new_topic.created_at else None
}
@router.put("/{topic_id}", response_model=dict)
async def update_topic(
topic_id: int,
topic_data: TopicUpdateRequest,
current_user: User = Depends(get_current_admin_user),
db: Session = Depends(get_db)
):
"""更新选题信息(管理员功能)"""
topic = db.query(Topic).filter(Topic.id == topic_id).first()
if not topic:
raise HTTPException(status_code=404, detail="选题不存在")
# 更新字段
if topic_data.title is not None:
topic.title = topic_data.title
if topic_data.field is not None:
topic.field = topic_data.field
if topic_data.priority_score is not None:
topic.priority_score = topic_data.priority_score
if topic_data.status is not None:
topic.status = topic_data.status
topic.updated_at = datetime.utcnow()
db.commit()
db.refresh(topic)
# 记录审计日志
create_audit_log(
db=db,
user_id=current_user.id,
action="update_topic",
resource_type="topic",
resource_id=topic_id,
details=f"状态更新为: {topic_data.status}"
)
return {
"id": topic.id,
"title": topic.title,
"status": topic.status,
"updated_at": topic.updated_at.isoformat() if topic.updated_at else None
}
@router.delete("/{topic_id}")
async def delete_topic(
topic_id: str,
current_user: User = Depends(get_current_admin_user),
db: Session = Depends(get_db)
):
"""删除选题(管理员功能)"""
topic = db.query(Topic).filter(Topic.id == topic_id).first()
if not topic:
raise HTTPException(status_code=404, detail="选题不存在")
db.delete(topic)
db.commit()
# 记录审计日志
create_audit_log(
db=db,
user_id=current_user.id,
action="delete_topic",
resource_type="topic",
resource_id=topic_id,
details="选题已删除"
)
return {"message": "选题已成功删除"}
@router.get("/{topic_id}", response_model=dict)
async def get_topic(
topic_id: int,
db: Session = Depends(get_db)
):
"""获取单个选题详情"""
topic = db.query(Topic).filter(Topic.id == topic_id).first()
if not topic:
raise HTTPException(status_code=404, detail="选题不存在")
return {
"id": topic.id,
"title": topic.title,
"field": topic.field,
"priority_score": topic.priority_score,
"status": topic.status,
"compliance_score": topic.compliance_score,
"created_at": topic.created_at.isoformat() if topic.created_at else None,
"updated_at": topic.updated_at.isoformat() if topic.updated_at else None,
"generated_at": topic.generated_at.isoformat() if topic.generated_at else None,
"published_at": topic.published_at.isoformat() if topic.published_at else None,
"platform_urls": topic.platform_urls
}
+3 -3
View File
@@ -100,7 +100,7 @@ def login(login_data: LoginRequest, request: Request, db: Session = Depends(get_
user_agent=user_agent,
db=db
)
return TokenResponse(token=token, role=user.role, user=UserResponse.from_orm(user))
return TokenResponse(token=token, role=user.role, user=UserResponse.model_validate(user))
# 从数据库查询其他用户
user = db.query(User).filter(User.username == login_data.username).first()
@@ -136,7 +136,7 @@ def login(login_data: LoginRequest, request: Request, db: Session = Depends(get_
user_agent=user_agent,
db=db
)
return TokenResponse(token=token, role=user.role, user=UserResponse.from_orm(user))
return TokenResponse(token=token, role=user.role, user=UserResponse.model_validate(user))
def get_current_user(request: Request, db: Session = Depends(get_db)) -> User:
"""依赖项:验证用户登录"""
@@ -159,5 +159,5 @@ def get_me(
current_user: User = Depends(get_current_user)
):
"""获取当前登录用户信息"""
return {"user": UserResponse.from_orm(current_user)}
return {"user": UserResponse.model_validate(current_user)}
+3 -6
View File
@@ -28,7 +28,7 @@ def list_cases(
):
"""获取案例列表(管理员)"""
cases = db.query(Case).all()
return [CaseResponse.from_orm(c) for c in cases]
return [CaseResponse.model_validate(c) for c in cases]
@router.get("/{case_id}", response_model=CaseResponse)
def get_case(
@@ -51,10 +51,7 @@ def create_case(
admin_user = Depends(get_current_admin)
):
"""创建新案例"""
existing = db.query(Case).filter(Case.id == case_data.id).first()
if existing:
raise HTTPException(status_code=400, detail="案例ID已存在")
case = Case(**case_data.dict())
case = Case(**case_data.model_dump())
db.add(case)
db.commit()
db.refresh(case)
@@ -72,7 +69,7 @@ def update_case(
case = db.query(Case).filter(Case.id == case_id).first()
if not case:
raise HTTPException(status_code=404, detail="案例不存在")
update_data = case_update.dict(exclude_unset=True)
update_data = case_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(case, field, value)
db.commit()
+3 -3
View File
@@ -28,7 +28,7 @@ def list_llm_configs(
):
"""获取 LLM 配置列表"""
configs = db.query(LLMConfig).all()
return [LLMConfigResponse.from_orm(c) for c in configs]
return [LLMConfigResponse.model_validate(c) for c in configs]
@router.get("/{config_id}", response_model=LLMConfigResponse)
def get_llm_config(
@@ -51,7 +51,7 @@ def create_llm_config(
admin_user = Depends(get_current_admin)
):
"""创建 LLM 配置"""
config = LLMConfig(**config_data.dict())
config = LLMConfig(**config_data.model_dump())
db.add(config)
db.commit()
db.refresh(config)
@@ -69,7 +69,7 @@ def update_llm_config(
config = db.query(LLMConfig).filter(LLMConfig.id == config_id).first()
if not config:
raise HTTPException(status_code=404, detail="配置不存在")
update_data = config_update.dict(exclude_unset=True)
update_data = config_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(config, field, value)
db.commit()
+2 -2
View File
@@ -11,7 +11,7 @@ router = APIRouter(prefix="/api", tags=["optimizer_logs"])
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent.parent
@router.post("/optimizer/run")
def run_optimizer(
async def run_optimizer(
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_admin)
@@ -20,7 +20,7 @@ def run_optimizer(
触发合规优化器运行(管理员)
"""
try:
body = request.json()
body = await request.json()
except Exception:
raise HTTPException(status_code=400, detail="Invalid JSON")
topic_id = body.get("topic_id")
+5 -5
View File
@@ -31,7 +31,7 @@ def list_system_configs(
# 将 value 解析为 JSON(如果是 JSON 字符串)
result = []
for c in configs:
resp = SystemConfigResponse.from_orm(c)
resp = SystemConfigResponse.model_validate(c)
# 尝试解析 value 为 JSON
if c.value:
try:
@@ -53,7 +53,7 @@ def get_system_config(
config = db.query(SystemConfig).filter(SystemConfig.key == config_key).first()
if not config:
raise HTTPException(status_code=404, detail="配置不存在")
resp = SystemConfigResponse.from_orm(config)
resp = SystemConfigResponse.model_validate(config)
if config.value:
try:
import json
@@ -85,7 +85,7 @@ def create_system_config(
existing.description = config_data.description
db.commit()
db.refresh(existing)
resp = SystemConfigResponse.from_orm(existing)
resp = SystemConfigResponse.model_validate(existing)
if existing.value:
try:
import json
@@ -95,7 +95,7 @@ def create_system_config(
return resp
else:
# 新建
data = config_data.dict()
data = config_data.model_dump()
# 将 value 转为字符串(如果是复杂类型则 JSON)
if isinstance(data.get('value'), (dict, list)):
import json
@@ -104,7 +104,7 @@ def create_system_config(
db.add(config)
db.commit()
db.refresh(config)
resp = SystemConfigResponse.from_orm(config)
resp = SystemConfigResponse.model_validate(config)
if config.value:
try:
import json
+3 -3
View File
@@ -38,7 +38,7 @@ def list_task_logs(
if status:
query = query.filter(TaskLog.status == status)
logs = query.order_by(TaskLog.started_at.desc()).all()
return [TaskLogResponse.from_orm(l) for l in logs]
return [TaskLogResponse.model_validate(l) for l in logs]
@router.get("/{log_id}", response_model=TaskLogResponse)
def get_task_log(
@@ -61,7 +61,7 @@ def create_task_log(
admin_user = Depends(get_current_admin)
):
"""创建任务日志(用于手动记录)"""
log = TaskLog(**log_data.dict())
log = TaskLog(**log_data.model_dump())
db.add(log)
db.commit()
db.refresh(log)
@@ -79,7 +79,7 @@ def update_task_log(
log = db.query(TaskLog).filter(TaskLog.id == log_id).first()
if not log:
raise HTTPException(status_code=404, detail="日志不存在")
update_data = log_update.dict(exclude_unset=True)
update_data = log_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(log, field, value)
db.commit()
+8
View File
@@ -38,6 +38,14 @@ Base = declarative_base()
def init_db():
Base.metadata.create_all(bind=engine)
# 迁移:为已有表添加 last_login 列
try:
from sqlalchemy import text
with engine.connect() as conn:
conn.execute(text("ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login TIMESTAMP"))
conn.commit()
except Exception:
pass # SQLite 不支持 IF NOT EXISTS,但 create_all 对 SQLite 够用
def get_db():
db = SessionLocal()
+13 -1
View File
@@ -41,7 +41,7 @@ def import_initial_data():
### 选题信息
标题:{topic.get('title')}
领域:{topic.get('field')}
领域:{topic.get('field_name')}
核心观点:{topic.get('core_concept', '')}
受众痛点:{topic.get('audience_pain', '')}
独特视角:{topic.get('unique_angle', '')}
@@ -209,6 +209,18 @@ def import_initial_data():
db.commit()
print(f"✅ 导入 {len(cases_data)} 条案例")
# 同步 PostgreSQL 自增序列
if os.getenv('USE_POSTGRES', 'true').lower() == 'true':
try:
from sqlalchemy import text
tables = ["cases", "users", "content_calendar", "media_assets", "content_metrics", "content_tasks", "audit_logs", "task_logs", "topic_config_fields"]
for table in tables:
db.execute(text(f"SELECT setval(pg_get_serial_sequence('{table}', 'id'), COALESCE((SELECT MAX(id) FROM {table}), 0) + 1, false)"))
db.commit()
print("✅ PostgreSQL 自增序列已同步")
except Exception as e:
print(f"⚠️ 序列同步警告: {e}")
print("✅ 初始化完成")
except Exception as e:
+2
View File
@@ -41,6 +41,7 @@ class User(Base):
username = Column(String, unique=True, nullable=False, index=True)
password_hash = Column(String, nullable=False)
role = Column(String, default="user", nullable=False)
last_login = Column(DateTime(timezone=True), nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
@@ -49,6 +50,7 @@ class User(Base):
"id": self.id,
"username": self.username,
"role": self.role,
"last_login": self.last_login.isoformat() if self.last_login else None,
"created_at": self.created_at.isoformat() if self.created_at else None
}
+2 -1
View File
@@ -441,7 +441,7 @@ class TaskLogBase(BaseModel):
message: Optional[str] = None
started_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
duration_seconds: Optional[int] = None
duration: Optional[int] = None
class TaskLogResponse(TaskLogBase):
@@ -475,6 +475,7 @@ class SystemConfigBase(BaseModel):
class SystemConfigResponse(SystemConfigBase):
value: Optional[Any] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
+19 -21
View File
@@ -1,26 +1,20 @@
# 宇之然内容创作平台 - 数据库配置
# 宇之然内容创作平台 - 数据库配置 (SQLite版本)
from sqlalchemy import create_engine
from sqlalchemy import create_engine, Column, String, Integer, Float, Date, DateTime, Text, Boolean, JSON, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import sessionmaker, relationship
from pathlib import Path
import os
from dotenv import load_dotenv
load_dotenv()
# 数据库URL(从环境变量读取)
SQLALCHEMY_DATABASE_URL = os.getenv(
"DATABASE_URL",
"postgresql://user:password@localhost:5432/yuzhiran_db"
)
# 使用SQLite数据库
BASE_DIR = Path(__file__).resolve().parent
DATABASE_URL = f"sqlite:///{BASE_DIR / 'data' / 'yzr.db'}"
# 创建数据库引擎
engine = create_engine(
SQLALCHEMY_DATABASE_URL,
pool_size=20,
max_overflow=30,
pool_pre_ping=True,
echo=False # 生产环境设为False
DATABASE_URL,
connect_args={"check_same_thread": False},
echo=False
)
# 会话工厂
@@ -29,6 +23,15 @@ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 基础模型类
Base = declarative_base()
# 导入所有模型
from app.models import *
# 创建所有表
def init_db():
"""初始化数据库(创建表)"""
Base.metadata.create_all(bind=engine)
# 获取数据库会话
def get_db():
"""获取数据库会话"""
db = SessionLocal()
@@ -36,8 +39,3 @@ def get_db():
yield db
finally:
db.close()
def init_db():
"""初始化数据库(创建表)"""
from app.models import Base
Base.metadata.create_all(bind=engine)
+97
View File
@@ -0,0 +1,97 @@
import sys
import os
sys.path.insert(0, os.path.dirname(__file__))
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from contextlib import asynccontextmanager
import uvicorn
from starlette.staticfiles import StaticFiles
from app.database import init_db
from core.security import SECRET_KEY
from api import auth, topics, system, publishing, articles, logs, admin
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期管理"""
print("正在初始化数据库...")
init_db()
print("数据库初始化完成")
yield
print("应用关闭")
app = FastAPI(
title="宇之然内容创作平台 API",
description="企业级内容创作管理系统",
version="1.0.0",
lifespan=lifespan
)
# CORS配置
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# API 路由(必须先于静态文件注册)
app.include_router(auth.router, prefix="/api/auth", tags=["认证"])
app.include_router(topics.router, prefix="/api/topics", tags=["选题管理"])
app.include_router(system.router, prefix="/api/system", tags=["系统状态"])
app.include_router(publishing.router, prefix="/api/publishing", tags=["文章发布"])
app.include_router(articles.router, prefix="/api/articles", tags=["文章预览"])
app.include_router(logs.router, prefix="/api/logs", tags=["日志系统"])
app.include_router(admin.router, prefix="/api/admin", tags=["管理员"])
# 健康检查
@app.get("/health")
async def health_check():
return {"status": "healthy", "timestamp": __import__('datetime').datetime.now().isoformat()}
# 独立页面路由(必须在 SPA catch-all 之前)
@app.get("/topics.html")
async def topics_page():
return FileResponse("static/topics.html")
@app.get("/logs.html")
async def logs_page():
return FileResponse("static/logs.html")
@app.get("/users.html")
async def users_page():
return FileResponse("static/users.html")
@app.get("/login.html")
async def login_page():
return FileResponse("static/login.html")
@app.get("/admin.html")
async def admin_page():
return FileResponse("static/admin.html")
# 静态文件(不干扰API
app.mount("/static", StaticFiles(directory="static"), name="static")
# SPA:所有非 API 路径返回 index.html(最后注册)
@app.get("/{full_path:path}")
async def serve_spa(full_path: str):
return FileResponse("static/index.html")
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
import traceback
print(f"全局异常: {exc}")
print(f"堆栈跟踪:\n{traceback.format_exc()}")
return {
"error": "服务器内部错误",
"message": str(exc),
"path": request.url.path
}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8001, reload=True, log_level="info")
+1
View File
@@ -0,0 +1 @@
../frontend