fix(webchat): migrate webchat data when dashboard username changes#7261
fix(webchat): migrate webchat data when dashboard username changes#7261Colourfulzi wants to merge 2 commits intoAstrBotDevs:masterfrom
Conversation
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
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- In
migrate_user_webchat_data, theConversationV2andPreferenceupdates could be constrained further (e.g.,LIKEon theold_fragment) so you only touch rows that actually contain the old username and avoid unnecessary writes on allwebchat%records. - Now that
AuthRoutedepends onBaseDatabase.migrate_user_webchat_data, ensure allBaseDatabaseimplementations (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.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
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.
| 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 | ||
| ) | ||
| ) | ||
| ) |
There was a problem hiding this comment.
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))
)| 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) |
There was a problem hiding this comment.
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.
| 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
- 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.
astrbot/core/db/__init__.py
Outdated
| # ==== | ||
| # ChatUI Project Management | ||
| # ==== | ||
| # ====" |
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 / 改动点
Screenshots or Test Results / 运行截图或测试结果
验证步骤:

1.进行多组会话。
2.修改用户名。
3.检查webui,chat模块下历史聊天记录是否迁移过来。会话记录是否存在。
4.检查“会话数据”页面中,关联webchat的会话记录的会话ID是否迁移正确。
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.txtandpyproject.toml./ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt和pyproject.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:
Enhancements: