136 lines
5.3 KiB
Python
136 lines
5.3 KiB
Python
"""SQLAlchemy 数据模型"""
|
||
from datetime import datetime
|
||
from sqlalchemy import (
|
||
create_engine, Column, Integer, String, Text, DateTime, LargeBinary,
|
||
Enum as SAEnum, ForeignKey, Table
|
||
)
|
||
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
|
||
from config import get_db_url
|
||
|
||
engine = create_engine(get_db_url(), echo=False, pool_pre_ping=True)
|
||
SessionLocal = sessionmaker(bind=engine, autocommit=False)
|
||
Base = declarative_base()
|
||
|
||
|
||
def get_db():
|
||
"""获取数据库会话(FastAPI 依赖注入)"""
|
||
db = SessionLocal()
|
||
try:
|
||
yield db
|
||
finally:
|
||
db.close()
|
||
|
||
|
||
# ===== 文件夹 =====
|
||
class Folder(Base):
|
||
"""文件夹表 — 笔记和网站共用"""
|
||
__tablename__ = "folders"
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
name = Column(String(100), nullable=False)
|
||
type = Column(SAEnum("note", "website", name="folder_type"), nullable=False, default="note")
|
||
parent_id = Column(Integer, ForeignKey("folders.id"), nullable=True)
|
||
sort_order = Column(Integer, default=0)
|
||
created_by = Column(String(100), nullable=False, default="admin")
|
||
created_at = Column(DateTime, default=datetime.utcnow)
|
||
|
||
children = relationship("Folder", backref="parent", remote_side=[id])
|
||
notes = relationship("Note", back_populates="folder", cascade="all, delete-orphan")
|
||
websites = relationship("Website", back_populates="folder", cascade="all, delete-orphan")
|
||
|
||
|
||
# ===== 笔记 =====
|
||
class Note(Base):
|
||
"""笔记表"""
|
||
__tablename__ = "notes"
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
title = Column(String(500), nullable=False, default="无标题")
|
||
content = Column(Text, nullable=True) # Tiptap 输出的 HTML
|
||
folder_id = Column(Integer, ForeignKey("folders.id"), nullable=True)
|
||
created_by = Column(String(100), nullable=False, default="admin")
|
||
updated_by = Column(String(100), nullable=False, default="admin")
|
||
share_token = Column(String(64), nullable=True, unique=True)
|
||
share_expires_at = Column(DateTime, nullable=True)
|
||
created_at = Column(DateTime, default=datetime.utcnow)
|
||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||
|
||
folder = relationship("Folder", back_populates="notes")
|
||
tags = relationship("Tag", secondary="note_tags", back_populates="notes")
|
||
|
||
|
||
# ===== 笔记-标签关联 =====
|
||
note_tags = Table(
|
||
"note_tags",
|
||
Base.metadata,
|
||
Column("note_id", Integer, ForeignKey("notes.id", ondelete="CASCADE"), primary_key=True),
|
||
Column("tag_id", Integer, ForeignKey("tags.id", ondelete="CASCADE"), primary_key=True),
|
||
)
|
||
|
||
|
||
# ===== 标签 =====
|
||
class Tag(Base):
|
||
"""标签表"""
|
||
__tablename__ = "tags"
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
name = Column(String(50), nullable=False, unique=True)
|
||
color = Column(String(20), default="#666666")
|
||
|
||
notes = relationship("Note", secondary="note_tags", back_populates="tags")
|
||
|
||
|
||
# ===== 网站 =====
|
||
class Website(Base):
|
||
"""网站表"""
|
||
__tablename__ = "websites"
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
name = Column(String(200), nullable=False)
|
||
url = Column(String(500), nullable=False)
|
||
folder_id = Column(Integer, ForeignKey("folders.id"), nullable=True)
|
||
created_by = Column(String(100), nullable=False, default="admin")
|
||
updated_by = Column(String(100), nullable=False, default="admin")
|
||
created_at = Column(DateTime, default=datetime.utcnow)
|
||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||
|
||
folder = relationship("Folder", back_populates="websites")
|
||
accounts = relationship("Account", back_populates="website", cascade="all, delete-orphan",
|
||
order_by="Account.sort_order")
|
||
|
||
|
||
# ===== 账号 =====
|
||
class Account(Base):
|
||
"""账号表 — 存储网站登录凭据"""
|
||
__tablename__ = "accounts"
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
website_id = Column(Integer, ForeignKey("websites.id", ondelete="CASCADE"), nullable=False)
|
||
username = Column(String(500), nullable=False)
|
||
password = Column(String(500), nullable=False) # 明文存储,后续加密
|
||
remark = Column(Text, nullable=True)
|
||
created_by = Column(String(100), nullable=False, default="admin")
|
||
updated_by = Column(String(100), nullable=False, default="admin")
|
||
sort_order = Column(Integer, default=0)
|
||
created_at = Column(DateTime, default=datetime.utcnow)
|
||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||
|
||
website = relationship("Website", back_populates="accounts")
|
||
|
||
|
||
# ===== 上传文件 =====
|
||
class Upload(Base):
|
||
"""文件上传表 — 图片存储(新文件存文件服务,旧文件兼容 DB blob)"""
|
||
__tablename__ = "uploads"
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
filename = Column(String(255), nullable=False)
|
||
content_type = Column(String(100), nullable=False)
|
||
data = Column(LargeBinary(length=2**32-1), nullable=True) # 旧图片 LONGBLOB(兼容),新文件为 NULL
|
||
file_id = Column(String(64), nullable=True) # 文件服务返回的 fileId(新文件)
|
||
size = Column(Integer, default=0)
|
||
created_by = Column(String(100), nullable=False, default="admin")
|
||
created_at = Column(DateTime, default=datetime.utcnow)
|
||
|
||
|