From 27432fa2e2e4feeddd9f5f8a300d7e13d5572dde Mon Sep 17 00:00:00 2001 From: bboysoul Date: Sat, 5 Sep 2026 20:29:47 +0800 Subject: [PATCH] perf: consolidate initial navigation loading --- backend/main.py | 25 +++++++++++++++++++ frontend/src/App.vue | 15 ++++++++++- .../0006_remove_duplicate_habit_index.py | 23 +++++++++++++++++ tests/test_mvp_backend.py | 17 +++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 migrations/versions/0006_remove_duplicate_habit_index.py diff --git a/backend/main.py b/backend/main.py index de6c60d..ad10bd5 100644 --- a/backend/main.py +++ b/backend/main.py @@ -173,6 +173,31 @@ async def me(user: User = Depends(current_user)): return user +@app.get("/api/v1/bootstrap") +async def bootstrap_data(user: User = Depends(current_user), db: AsyncSession = Depends(get_db)): + folders = list((await db.scalars( + select(Folder) + .where(Folder.user_id == user.id, Folder.deleted_at.is_(None)) + .order_by(Folder.position, Folder.created_at) + )).all()) + lists = list((await db.scalars( + select(TaskList) + .where(TaskList.user_id == user.id, TaskList.deleted_at.is_(None)) + .order_by(TaskList.is_inbox.desc(), TaskList.position, TaskList.created_at) + )).all()) + tags = list((await db.scalars( + select(Tag).where(Tag.user_id == user.id).order_by(Tag.name, Tag.id) + )).all()) + inbox = next((item for item in lists if item.is_inbox), None) + return { + "user": UserOut.model_validate(user), + "folders": [FolderOut.model_validate(item) for item in folders], + "lists": [ListOut.model_validate(item) for item in lists], + "tags": [TagOut.model_validate(item) for item in tags], + "inbox_id": inbox.id if inbox else None, + } + + @app.post("/api/v1/auth/logout", status_code=204) async def logout( response: Response, diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 077f1c3..98b1d61 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -107,8 +107,14 @@ async function bootstrap() { const status = await api('/setup/status') initialized.value = status.initialized if (status.initialized) { - await api('/me') + const data = await api('/bootstrap') authenticated.value = true + folders.value = data.folders ?? [] + lists.value = data.lists ?? [] + tags.value = data.tags ?? [] + navigationLoaded.value = true + activeList.value = data.inbox_id || lists.value.find((item: TaskList) => item.is_inbox)?.id || lists.value[0]?.id || '' + expandedFolders.value = new Set(folders.value.map((folder) => folder.id)) await loadAll() } } catch { authenticated.value = false; initialized.value ??= true } @@ -119,6 +125,13 @@ async function submitAuth() { const path = initialized.value ? '/auth/login' : '/setup/initialize' await api(path, { method: 'POST', body: JSON.stringify({ username: username.value, password: password.value }) }) initialized.value = true; authenticated.value = true + const data = await api('/bootstrap') + folders.value = data.folders ?? [] + lists.value = data.lists ?? [] + tags.value = data.tags ?? [] + navigationLoaded.value = true + activeList.value = data.inbox_id || lists.value.find((item: TaskList) => item.is_inbox)?.id || lists.value[0]?.id || '' + expandedFolders.value = new Set(folders.value.map((folder) => folder.id)) await loadAll() } catch (reason) { fail(reason) } } diff --git a/migrations/versions/0006_remove_duplicate_habit_index.py b/migrations/versions/0006_remove_duplicate_habit_index.py new file mode 100644 index 0000000..3cfa3f6 --- /dev/null +++ b/migrations/versions/0006_remove_duplicate_habit_index.py @@ -0,0 +1,23 @@ +"""remove duplicate habit log index +Revision ID: 0006 +Revises: 0005 +""" + +from alembic import op + +revision = "0006" +down_revision = "0005" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.drop_index("ix_habit_logs_habit_day", table_name="habit_logs") + + +def downgrade() -> None: + op.create_index( + "ix_habit_logs_habit_day", + "habit_logs", + ["habit_id", "day"], + ) diff --git a/tests/test_mvp_backend.py b/tests/test_mvp_backend.py index 0f8bbff..229ff13 100644 --- a/tests/test_mvp_backend.py +++ b/tests/test_mvp_backend.py @@ -12,6 +12,23 @@ def boot(client): return client.get("/api/v1/lists").json()[0] +def test_bootstrap_returns_navigation_and_current_user(client): + inbox = boot(client) + client.post("/api/v1/folders", json={"name": "工作"}) + client.post("/api/v1/tags", json={"name": "重要", "color": "#ff0000"}) + client.post("/api/v1/tasks", json={"title": "首屏任务", "list_id": inbox["id"]}) + + response = client.get("/api/v1/bootstrap") + + assert response.status_code == 200 + data = response.json() + assert data["user"]["username"] == "owner" + assert any(row["id"] == inbox["id"] for row in data["lists"]) + assert [row["name"] for row in data["folders"]] == ["工作"] + assert [row["name"] for row in data["tags"]] == ["重要"] + assert data["inbox_id"] == inbox["id"] + + def test_recurring_calendar_exceptions_and_scopes(client): inbox = boot(client) task = client.post(