初始化
This commit is contained in:
135
backend/models.py
Normal file
135
backend/models.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user