This commit is contained in:
@@ -173,6 +173,31 @@ async def me(user: User = Depends(current_user)):
|
|||||||
return 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)
|
@app.post("/api/v1/auth/logout", status_code=204)
|
||||||
async def logout(
|
async def logout(
|
||||||
response: Response,
|
response: Response,
|
||||||
|
|||||||
+14
-1
@@ -107,8 +107,14 @@ async function bootstrap() {
|
|||||||
const status = await api('/setup/status')
|
const status = await api('/setup/status')
|
||||||
initialized.value = status.initialized
|
initialized.value = status.initialized
|
||||||
if (status.initialized) {
|
if (status.initialized) {
|
||||||
await api('/me')
|
const data = await api('/bootstrap')
|
||||||
authenticated.value = true
|
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()
|
await loadAll()
|
||||||
}
|
}
|
||||||
} catch { authenticated.value = false; initialized.value ??= true }
|
} catch { authenticated.value = false; initialized.value ??= true }
|
||||||
@@ -119,6 +125,13 @@ async function submitAuth() {
|
|||||||
const path = initialized.value ? '/auth/login' : '/setup/initialize'
|
const path = initialized.value ? '/auth/login' : '/setup/initialize'
|
||||||
await api(path, { method: 'POST', body: JSON.stringify({ username: username.value, password: password.value }) })
|
await api(path, { method: 'POST', body: JSON.stringify({ username: username.value, password: password.value }) })
|
||||||
initialized.value = true; authenticated.value = true
|
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()
|
await loadAll()
|
||||||
} catch (reason) { fail(reason) }
|
} catch (reason) { fail(reason) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"],
|
||||||
|
)
|
||||||
@@ -12,6 +12,23 @@ def boot(client):
|
|||||||
return client.get("/api/v1/lists").json()[0]
|
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):
|
def test_recurring_calendar_exceptions_and_scopes(client):
|
||||||
inbox = boot(client)
|
inbox = boot(client)
|
||||||
task = client.post(
|
task = client.post(
|
||||||
|
|||||||
Reference in New Issue
Block a user