split files, database and S3, models
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||||
|
from .config import settings
|
||||||
|
|
||||||
|
engine = create_engine(settings.database_url)
|
||||||
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
# 获取数据库连接的依赖项
|
||||||
|
def get_db():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
+36
-75
@@ -1,58 +1,22 @@
|
|||||||
import uuid
|
import uuid
|
||||||
import boto3
|
|
||||||
from fastapi import FastAPI, HTTPException, Depends
|
from fastapi import FastAPI, HTTPException, Depends
|
||||||
from sqlalchemy import create_engine, Column, String, ForeignKey
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy.orm import sessionmaker, Session, declarative_base, relationship
|
|
||||||
|
|
||||||
from app.config import settings
|
from .database import engine, Base, get_db
|
||||||
|
from .models import Project, Markdown
|
||||||
# --- 数据库初始化 ---
|
from .s3 import upload_to_s3, delete_from_s3
|
||||||
engine = create_engine(settings.database_url)
|
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
||||||
Base = declarative_base()
|
|
||||||
|
|
||||||
# --- S3 客户端初始化 ---
|
|
||||||
s3_client = boto3.client(
|
|
||||||
's3',
|
|
||||||
aws_access_key_id=settings.s3_access_key,
|
|
||||||
aws_secret_access_key=settings.s3_secret_key,
|
|
||||||
endpoint_url=settings.s3_endpoint,
|
|
||||||
region_name=settings.s3_region
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- 数据模型 ---
|
|
||||||
class Project(Base):
|
|
||||||
__tablename__ = "projects"
|
|
||||||
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
||||||
name = Column(String, nullable=False)
|
|
||||||
# cascade="all, delete-orphan" 确保在 db.delete(project) 时自动删除关联的 Markdown 记录
|
|
||||||
markdowns = relationship("Markdown", back_populates="project", cascade="all, delete-orphan")
|
|
||||||
|
|
||||||
class Markdown(Base):
|
|
||||||
__tablename__ = "markdowns"
|
|
||||||
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
||||||
title = Column(String, nullable=False)
|
|
||||||
s3_key = Column(String, nullable=False)
|
|
||||||
project_id = Column(String, ForeignKey("projects.id", ondelete="CASCADE"))
|
|
||||||
project = relationship("Project", back_populates="markdowns")
|
|
||||||
|
|
||||||
|
# 启动时创建表
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI(title="AI Notion Backend")
|
||||||
|
|
||||||
def get_db():
|
|
||||||
db = SessionLocal()
|
|
||||||
try:
|
|
||||||
yield db
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
# --- API 接口 ---
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
def health():
|
def health():
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
# --- Project 接口 ---
|
||||||
|
|
||||||
@app.post("/projects")
|
@app.post("/projects")
|
||||||
def create_project(name: str, db: Session = Depends(get_db)):
|
def create_project(name: str, db: Session = Depends(get_db)):
|
||||||
project = Project(name=name)
|
project = Project(name=name)
|
||||||
@@ -61,43 +25,40 @@ def create_project(name: str, db: Session = Depends(get_db)):
|
|||||||
db.refresh(project)
|
db.refresh(project)
|
||||||
return project
|
return project
|
||||||
|
|
||||||
@app.post("/projects/{project_id}/markdowns")
|
|
||||||
def upload_markdown(project_id: str, title: str, content: str, db: Session = Depends(get_db)):
|
|
||||||
project = db.query(Project).filter(Project.id == project_id).first()
|
|
||||||
if not project:
|
|
||||||
raise HTTPException(status_code=404, detail="Project not found")
|
|
||||||
|
|
||||||
# 1. 存入 S3
|
|
||||||
s3_key = f"projects/{project_id}/{uuid.uuid4()}.md"
|
|
||||||
s3_client.put_object(
|
|
||||||
Bucket=settings.s3_bucket_name,
|
|
||||||
Key=s3_key,
|
|
||||||
Body=content,
|
|
||||||
ContentType="text/markdown"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 2. 存入数据库
|
|
||||||
md_record = Markdown(title=title, s3_key=s3_key, project_id=project_id)
|
|
||||||
db.add(md_record)
|
|
||||||
db.commit()
|
|
||||||
return {"id": md_record.id, "s3_key": s3_key}
|
|
||||||
|
|
||||||
@app.delete("/projects/{project_id}")
|
@app.delete("/projects/{project_id}")
|
||||||
def delete_project(project_id: str, db: Session = Depends(get_db)):
|
def delete_project(project_id: str, db: Session = Depends(get_db)):
|
||||||
project = db.query(Project).filter(Project.id == project_id).first()
|
project = db.query(Project).filter(Project.id == project_id).first()
|
||||||
if not project:
|
if not project:
|
||||||
raise HTTPException(status_code=404, detail="Project not found")
|
raise HTTPException(status_code=404, detail="Project not found")
|
||||||
|
|
||||||
# 1. 清理 S3 中的文件
|
# 1. 获取该项目下所有 Markdown 的 S3 keys 并从 S3 删除
|
||||||
markdowns = db.query(Markdown).filter(Markdown.project_id == project_id).all()
|
s3_keys = [md.s3_key for md in project.markdowns]
|
||||||
if markdowns:
|
delete_from_s3(s3_keys)
|
||||||
keys = [{'Key': md.s3_key} for md in markdowns]
|
|
||||||
s3_client.delete_objects(
|
|
||||||
Bucket=settings.s3_bucket_name,
|
|
||||||
Delete={'Objects': keys}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 2. 从数据库删除项目 (SQLAlchemy 会自动处理 markdowns 表的级联删除)
|
# 2. 从数据库删除项目(SQLAlchemy 会自动级联删除对应的 Markdown 记录)
|
||||||
db.delete(project)
|
db.delete(project)
|
||||||
db.commit()
|
db.commit()
|
||||||
return {"message": f"Project {project_id} and its files have been purged."}
|
return {"message": f"Project {project_id} and associated files purged."}
|
||||||
|
|
||||||
|
# --- Markdown 接口 ---
|
||||||
|
|
||||||
|
@app.post("/projects/{project_id}/markdowns")
|
||||||
|
def upload_markdown(project_id: str, title: str, content: str, db: Session = Depends(get_db)):
|
||||||
|
# 验证项目是否存在
|
||||||
|
project = db.query(Project).filter(Project.id == project_id).first()
|
||||||
|
if not project:
|
||||||
|
raise HTTPException(status_code=404, detail="Project not found")
|
||||||
|
|
||||||
|
# 生成 S3 路径
|
||||||
|
s3_key = f"projects/{project_id}/{uuid.uuid4()}.md"
|
||||||
|
|
||||||
|
# 1. 业务逻辑分离:上传 S3
|
||||||
|
upload_to_s3(s3_key, content)
|
||||||
|
|
||||||
|
# 2. 存入数据库记录
|
||||||
|
md_record = Markdown(title=title, s3_key=s3_key, project_id=project_id)
|
||||||
|
db.add(md_record)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(md_record)
|
||||||
|
|
||||||
|
return md_record
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import uuid
|
||||||
|
from sqlalchemy import Column, String, ForeignKey
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
from .database import Base
|
||||||
|
|
||||||
|
class Project(Base):
|
||||||
|
__tablename__ = "projects"
|
||||||
|
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
# 级联删除
|
||||||
|
markdowns = relationship("Markdown", back_populates="project", cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
class Markdown(Base):
|
||||||
|
__tablename__ = "markdowns"
|
||||||
|
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||||
|
title = Column(String, nullable=False)
|
||||||
|
s3_key = Column(String, nullable=False)
|
||||||
|
project_id = Column(String, ForeignKey("projects.id", ondelete="CASCADE"))
|
||||||
|
project = relationship("Project", back_populates="markdowns")
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import boto3
|
||||||
|
from .config import settings
|
||||||
|
|
||||||
|
s3_client = boto3.client(
|
||||||
|
's3',
|
||||||
|
aws_access_key_id=settings.s3_access_key,
|
||||||
|
aws_secret_access_key=settings.s3_secret_key,
|
||||||
|
endpoint_url=settings.s3_endpoint,
|
||||||
|
region_name=settings.s3_region
|
||||||
|
)
|
||||||
|
|
||||||
|
def upload_to_s3(key: str, content: str):
|
||||||
|
s3_client.put_object(
|
||||||
|
Bucket=settings.s3_bucket_name,
|
||||||
|
Key=key,
|
||||||
|
Body=content,
|
||||||
|
ContentType="text/markdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete_from_s3(keys: list[str]):
|
||||||
|
if not keys:
|
||||||
|
return
|
||||||
|
s3_client.delete_objects(
|
||||||
|
Bucket=settings.s3_bucket_name,
|
||||||
|
Delete={'Objects': [{'Key': k} for k in keys]}
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user