Files
john 05e173b293 Ship native iOS app, Wiki/TTS backend, and skeleton loading UX.
Replace the WebView shell with SwiftUI screens, add account-scoped Wiki and TTS APIs with adaptive review and photo scan support, and keep web/iOS pages usable while data loads asynchronously.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 15:13:59 +08:00

224 lines
9.4 KiB
Python

from sqlalchemy import Column, Float, 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)
error_bank_member = Column(Integer, nullable=False, default=0)
error_reinforce_streak = Column(Integer, nullable=False, default=0)
error_bank_entered_at = Column(String(32), nullable=True)
total_train_seconds = Column(Integer, nullable=False, default=0)
difficulty = Column(Float, nullable=False, default=5.0)
stability_hours = Column(Float, nullable=False, default=24.0)
next_review_at = Column(String(32), nullable=True)
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)
error_clear_correct_count = Column(Integer, nullable=False, default=2)
active_book_id = Column(Integer, ForeignKey("word_books.id"), nullable=True)
ai_wiki_enabled = Column(Integer, nullable=False, default=0)
ai_wiki_auto_organize = Column(Integer, nullable=False, default=0)
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)
error_clear_correct_count = 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)
class WikiSource(Base):
"""Immutable learning event ingested into a user's Wiki."""
__tablename__ = "wiki_sources"
__table_args__ = (
UniqueConstraint("user_id", "source_key", name="uq_wiki_source_user_key"),
)
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=True, index=True)
source_key = Column(String(96), nullable=False)
source_type = Column(String(32), nullable=False)
payload = Column(Text, nullable=False)
created_at = Column(String(32), nullable=False)
class WikiPage(Base):
"""Compiled Markdown page owned by one user."""
__tablename__ = "wiki_pages"
__table_args__ = (UniqueConstraint("user_id", "slug", name="uq_wiki_page_user_slug"),)
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=True, index=True)
slug = Column(String(160), nullable=False)
title = Column(String(500), nullable=False)
category = Column(String(32), nullable=False)
summary = Column(Text, nullable=False)
content = Column(Text, nullable=False)
source_count = Column(Integer, nullable=False, default=0)
created_at = Column(String(32), nullable=False)
updated_at = Column(String(32), nullable=False)
class WikiLink(Base):
__tablename__ = "wiki_links"
__table_args__ = (
UniqueConstraint(
"user_id",
"source_page_id",
"target_page_id",
"relation",
name="uq_wiki_link_edge",
),
)
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
source_page_id = Column(Integer, ForeignKey("wiki_pages.id"), nullable=False, index=True)
target_page_id = Column(Integer, ForeignKey("wiki_pages.id"), nullable=False, index=True)
relation = Column(String(32), nullable=False)
created_at = Column(String(32), nullable=False)
class WikiLog(Base):
__tablename__ = "wiki_logs"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
action = Column(String(32), nullable=False)
page_slug = Column(String(160), nullable=True)
detail = Column(Text, nullable=False)
created_at = Column(String(32), nullable=False)
class WikiLlmUsage(Base):
"""Per-account daily DeepSeek call counter for Wiki compilation."""
__tablename__ = "wiki_llm_usage"
__table_args__ = (
UniqueConstraint("user_id", "usage_date", name="uq_wiki_llm_usage_day"),
)
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
usage_date = Column(String(10), nullable=False)
call_count = Column(Integer, nullable=False, default=0)