e76fa586f1
Ship dual-track learning (daily accumulation vs textbook),沪教/商务词书 APIs and UI, native iOS wrapper with bundled H5, and production book import on deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
133 lines
5.7 KiB
Python
133 lines
5.7 KiB
Python
from sqlalchemy import Column, ForeignKey, Integer, String, Text, UniqueConstraint
|
|
|
|
from database import Base
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
username = Column(String(50), unique=True, nullable=False, index=True)
|
|
password_hash = Column(String(255), nullable=False)
|
|
created_at = Column(String(32), nullable=False)
|
|
|
|
|
|
class WordBook(Base):
|
|
"""全局词书目录(部署时导入)。"""
|
|
|
|
__tablename__ = "word_books"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
slug = Column(String(64), unique=True, nullable=False, index=True)
|
|
title = Column(String(128), nullable=False)
|
|
description = Column(Text, nullable=True)
|
|
level = Column(String(32), nullable=False)
|
|
word_count = Column(Integer, nullable=False, default=0)
|
|
unit_count = Column(Integer, nullable=False, default=0)
|
|
sort_order = Column(Integer, nullable=False, default=0)
|
|
daily_target = Column(Integer, nullable=False, default=15)
|
|
master_required_count = Column(Integer, nullable=False, default=4)
|
|
weak_wrong_threshold = Column(Integer, nullable=False, default=2)
|
|
learn_mode = Column(String(32), nullable=False, default="sequential")
|
|
is_published = Column(Integer, nullable=False, default=1)
|
|
|
|
|
|
class WordBookEntry(Base):
|
|
__tablename__ = "word_book_entries"
|
|
__table_args__ = (UniqueConstraint("book_id", "lemma_en", name="uq_book_lemma"),)
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
book_id = Column(Integer, ForeignKey("word_books.id"), nullable=False, index=True)
|
|
unit = Column(Integer, nullable=False, default=1)
|
|
unit_title = Column(String(128), nullable=True)
|
|
sort_index = Column(Integer, nullable=False, index=True)
|
|
lemma_en = Column(String(128), nullable=False)
|
|
zh = Column(String(256), nullable=False)
|
|
phonetic = Column(String(128), nullable=True)
|
|
example_en = Column(Text, nullable=True)
|
|
example_cn = Column(Text, nullable=True)
|
|
|
|
|
|
class Word(Base):
|
|
__tablename__ = "words"
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"user_id", "book_id", "source_text", "target_text", name="uq_user_book_word"
|
|
),
|
|
)
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
|
book_id = Column(Integer, nullable=False, default=0, index=True)
|
|
book_entry_id = Column(Integer, ForeignKey("word_book_entries.id"), nullable=True)
|
|
source_text = Column(String(500), nullable=False)
|
|
target_text = Column(String(500), nullable=False)
|
|
source_lang = Column(String(8), nullable=False)
|
|
target_lang = Column(String(8), nullable=False)
|
|
phonetic = Column(String(128), nullable=True)
|
|
example_en = Column(Text, nullable=True)
|
|
example_cn = Column(Text, nullable=True)
|
|
status = Column(String(32), nullable=False, default="new")
|
|
correct_count = Column(Integer, nullable=False, default=0)
|
|
wrong_count = Column(Integer, nullable=False, default=0)
|
|
consecutive_correct_count = Column(Integer, nullable=False, default=0)
|
|
mastery_score = Column(Integer, nullable=False, default=0)
|
|
train_count = Column(Integer, nullable=False, default=0)
|
|
total_train_seconds = Column(Integer, nullable=False, default=0)
|
|
review_due_date = Column(String(32), nullable=True)
|
|
last_reviewed_at = Column(String(32), nullable=True)
|
|
created_at = Column(String(32), nullable=False)
|
|
|
|
|
|
class QuizRecord(Base):
|
|
__tablename__ = "quiz_records"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
|
word_id = Column(Integer, ForeignKey("words.id"), nullable=False)
|
|
question_type = Column(String(32), nullable=False)
|
|
user_answer = Column(String(500), nullable=False)
|
|
correct_answer = Column(String(500), nullable=False)
|
|
is_correct = Column(Integer, nullable=False)
|
|
duration_seconds = Column(Integer, nullable=False, default=0)
|
|
created_at = Column(String(32), nullable=False)
|
|
|
|
|
|
class UserSettings(Base):
|
|
__tablename__ = "user_settings"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
user_id = Column(Integer, ForeignKey("users.id"), unique=True, nullable=False)
|
|
daily_target = Column(Integer, nullable=False, default=20)
|
|
master_required_count = Column(Integer, nullable=False, default=3)
|
|
weak_wrong_threshold = Column(Integer, nullable=False, default=3)
|
|
active_book_id = Column(Integer, ForeignKey("word_books.id"), nullable=True)
|
|
|
|
|
|
class UserBookSettings(Base):
|
|
"""用户对某本词书的练习参数(每本书独立保存)。"""
|
|
|
|
__tablename__ = "user_book_settings"
|
|
__table_args__ = (UniqueConstraint("user_id", "book_id", name="uq_user_book_settings"),)
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
|
book_id = Column(Integer, ForeignKey("word_books.id"), nullable=False, index=True)
|
|
daily_target = Column(Integer, nullable=False, default=12)
|
|
master_required_count = Column(Integer, nullable=False, default=3)
|
|
weak_wrong_threshold = Column(Integer, nullable=False, default=2)
|
|
|
|
|
|
class DictionaryEntry(Base):
|
|
"""全局离线翻译词典,与用户个人 words 表分离。"""
|
|
|
|
__tablename__ = "dictionary_entries"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
lemma_en = Column(String(128), unique=True, nullable=False, index=True)
|
|
zh = Column(String(256), nullable=False, index=True)
|
|
phonetic = Column(String(128), nullable=True)
|
|
example_en = Column(Text, nullable=True)
|
|
example_cn = Column(Text, nullable=True)
|
|
source = Column(String(64), nullable=True)
|