Skip to content

fix(webchat): migrate webchat data when dashboard username changes#7261

Open
Colourfulzi wants to merge 2 commits intoAstrBotDevs:masterfrom
Colourfulzi:fix/issue-7242-migrate-chat-history
Open

fix(webchat): migrate webchat data when dashboard username changes#7261
Colourfulzi wants to merge 2 commits intoAstrBotDevs:masterfrom
Colourfulzi:fix/issue-7242-migrate-chat-history

Conversation

@Colourfulzi
Copy link
Copy Markdown

@Colourfulzi Colourfulzi commented Apr 1, 2026

When dashboard account username is updated, existing webchat records still reference the old username in UMO-like identifiers, causing chat history to appear empty after re-login.

This change:

  • adds a database migration contract for username-based webchat data updates

  • implements SQLite migration for related user-scoped records

  • triggers migration in account edit flow before saving config

  • injects database dependency into AuthRoute for migration execution

Closes #7242

Modifications / 改动点

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

验证步骤:
1.进行多组会话。
2.修改用户名。
3.检查webui,chat模块下历史聊天记录是否迁移过来。会话记录是否存在。
4.检查“会话数据”页面中,关联webchat的会话记录的会话ID是否迁移正确。
PixPin_2026-04-01_16-58-05


Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Ensure webchat data linked to a dashboard account stays consistent when the account username changes.

Bug Fixes:

  • Preserve existing webchat conversations and preferences after a dashboard username change by updating stored identifiers and ownership fields.

Enhancements:

  • Introduce a database-agnostic migration contract for username-based webchat data updates and implement it for the SQLite backend.
  • Inject the database dependency into the authentication routes so account updates can trigger webchat data migrations atomically.

When dashboard account username is updated, existing webchat records still reference the old username in UMO-like identifiers, causing chat history to appear empty after re-login.

This change:

- adds a database migration contract for username-based webchat data updates

- implements SQLite migration for related user-scoped records

- triggers migration in account edit flow before saving config

- injects database dependency into AuthRoute for migration execution

Closes AstrBotDevs#7242
@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Apr 1, 2026
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • In migrate_user_webchat_data, the ConversationV2 and Preference updates could be constrained further (e.g., LIKE on the old_fragment) so you only touch rows that actually contain the old username and avoid unnecessary writes on all webchat% records.
  • Now that AuthRoute depends on BaseDatabase.migrate_user_webchat_data, ensure all BaseDatabase implementations (not just SQLite) either implement this method or explicitly no-op, otherwise non-SQLite backends will break at runtime.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `migrate_user_webchat_data`, the `ConversationV2` and `Preference` updates could be constrained further (e.g., `LIKE` on the `old_fragment`) so you only touch rows that actually contain the old username and avoid unnecessary writes on all `webchat%` records.
- Now that `AuthRoute` depends on `BaseDatabase.migrate_user_webchat_data`, ensure all `BaseDatabase` implementations (not just SQLite) either implement this method or explicitly no-op, otherwise non-SQLite backends will break at runtime.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@dosubot dosubot bot added area:webui The bug / feature is about webui(dashboard) of astrbot. feature:chatui The bug / feature is about astrbot's chatui, webchat labels Apr 1, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a migration process for webchat user data when a dashboard username is changed, adding the migrate_user_webchat_data method to the database interface and its SQLite implementation. The feedback identifies several missing tables and columns that should be included in the migration to maintain data integrity, suggests reordering the configuration update to ensure atomicity in case of migration failure, and points out a minor typo in the database interface file.

Comment on lines +1628 to +1658
await session.execute(
update(PlatformSession)
.where(col(PlatformSession.creator) == old_username)
.values(creator=new_username)
)
await session.execute(
update(ChatUIProject)
.where(col(ChatUIProject.creator) == old_username)
.values(creator=new_username)
)
await session.execute(
update(ConversationV2)
.where(col(ConversationV2.user_id).like("webchat%"))
.values(
user_id=func.replace(
ConversationV2.user_id, old_fragment, new_fragment
)
)
)
await session.execute(
update(Preference)
.where(
col(Preference.scope) == "umo",
col(Preference.scope_id).like("webchat%"),
)
.values(
scope_id=func.replace(
Preference.scope_id, old_fragment, new_fragment
)
)
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The migration is missing several critical tables and columns that store webchat user identifiers. Specifically, PlatformMessageHistory.user_id, ProviderStat.umo, PlatformSession.session_id, and SessionProjectRelation.session_id should also be updated to ensure chat history, statistics, and project associations remain consistent after a username change. Additionally, adding a more specific filter to the WHERE clause (checking for the presence of old_fragment) improves efficiency and prevents unnecessary updates.

                await session.execute(
                    update(PlatformSession)
                    .where(col(PlatformSession.creator) == old_username)
                    .values(creator=new_username)
                )
                await session.execute(
                    update(PlatformSession)
                    .where(col(PlatformSession.platform_id) == "webchat", col(PlatformSession.session_id).contains(old_fragment))
                    .values(session_id=func.replace(PlatformSession.session_id, old_fragment, new_fragment))
                )
                await session.execute(
                    update(ChatUIProject)
                    .where(col(ChatUIProject.creator) == old_username)
                    .values(creator=new_username)
                )
                await session.execute(
                    update(ConversationV2)
                    .where(col(ConversationV2.user_id).like("webchat%"), col(ConversationV2.user_id).contains(old_fragment))
                    .values(user_id=func.replace(ConversationV2.user_id, old_fragment, new_fragment))
                )
                await session.execute(
                    update(PlatformMessageHistory)
                    .where(col(PlatformMessageHistory.platform_id) == "webchat", col(PlatformMessageHistory.user_id).contains(old_fragment))
                    .values(user_id=func.replace(PlatformMessageHistory.user_id, old_fragment, new_fragment))
                )
                await session.execute(
                    update(Preference)
                    .where(col(Preference.scope) == "umo", col(Preference.scope_id).contains(old_fragment))
                    .values(scope_id=func.replace(Preference.scope_id, old_fragment, new_fragment))
                )
                await session.execute(
                    update(SessionProjectRelation)
                    .where(col(SessionProjectRelation.session_id).contains(old_fragment))
                    .values(session_id=func.replace(SessionProjectRelation.session_id, old_fragment, new_fragment))
                )
                await session.execute(
                    update(ProviderStat)
                    .where(col(ProviderStat.umo).contains(old_fragment))
                    .values(umo=func.replace(ProviderStat.umo, old_fragment, new_fragment))
                )

Comment on lines +78 to +84
old_username = self.config["dashboard"]["username"]
if new_username:
self.config["dashboard"]["username"] = new_username

# Migrate webchat user data before saving config to keep them in sync.
if new_username and new_username != old_username:
await self.db.migrate_user_webchat_data(old_username, new_username)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The dashboard username is updated in the self.config object before the database migration is executed. If the migration fails (e.g., due to a database lock or constraint violation), the in-memory configuration will already hold the new username. On a subsequent retry, old_username will match new_username, causing the migration logic to be skipped entirely, leaving the database in an inconsistent state. The configuration should only be updated after the migration succeeds.

Suggested change
old_username = self.config["dashboard"]["username"]
if new_username:
self.config["dashboard"]["username"] = new_username
# Migrate webchat user data before saving config to keep them in sync.
if new_username and new_username != old_username:
await self.db.migrate_user_webchat_data(old_username, new_username)
old_username = self.config["dashboard"]["username"]
# Migrate webchat user data before updating config to ensure atomicity and prevent inconsistent state on failure.
if new_username and new_username != old_username:
await self.db.migrate_user_webchat_data(old_username, new_username)
if new_username:
self.config["dashboard"]["username"] = new_username
References
  1. In a single-threaded asyncio event loop, synchronous blocks are atomic, but atomicity is lost across 'await' points. State changes should be ordered to maintain consistency in case of failures during asynchronous operations.

# ====
# ChatUI Project Management
# ====
# ===="
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a typo in the comment (trailing double quote).

Suggested change
# ===="
# ====

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:webui The bug / feature is about webui(dashboard) of astrbot. feature:chatui The bug / feature is about astrbot's chatui, webchat size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]WebChat模块:修改用户名后不会将历史聊天数据迁移到新用户名下

1 participant