feat(acl): redesign ACL with conversation scope selector (#297)

Backend
- New subject kinds: all / channel_identity / channel_type
- Source scope fields on bot_acl_rules: source_channel,
  source_conversation_type, source_conversation_id, source_thread_id
- Fix source_scope_check constraint: resolve source_channel server-side
  (channel_type → subject_channel_type; channel_identity → DB lookup)
- Add GET /bots/:id/acl/channel-types/:type/conversations to list
  observed conversations by platform type
- ListObservedConversations: include private/DM chats, normalise
  conversation_type; COALESCE(name, handle) for display name
- enrichConversationAvatar: persist entry.Name → conversation_name
  (keeps Telegram group titles current on every message)
- Unify Priority type to int32 across Go types to match DB INTEGER;
  remove all int/int32 casts in service layer
- Fix duplicate nil guard in Evaluate; drop dead SourceScope.Channel field
- Migration 0048_acl_redesign

Frontend
- Drag-and-drop rule priority reordering (SortableJS/useSortable);
  fix reorder: compute new order from oldIndex/newIndex directly,
  not from the array (which useSortable syncs after onEnd)
- Conversation scope selector: searchable popover backed by observed
  conversations (by identity or platform type); collapsible manual-ID fallback
- Display: name as primary label, stable channel·type·id always shown
  as subtitle for verification
- bot-terminal: accessibility fix on close-tab button (keyboard events)
- i18n: drag-to-reorder, conversation source, manual IDs (en/zh)

Tests: update fakeChatACL to Evaluate interface; fix SourceScope literals.
SDK/spec regenerated.
This commit is contained in:
BBQ
2026-03-28 01:06:13 +08:00
committed by GitHub
parent 64378d29ed
commit 7f9d6e4aba
30 changed files with 4599 additions and 3556 deletions
+95 -4
View File
@@ -134,6 +134,9 @@ importers:
'@vueuse/core':
specifier: ^14.1.0
version: 14.1.0(vue@3.5.26(typescript@5.9.3))
'@vueuse/integrations':
specifier: ^14.2.1
version: 14.2.1(axios@1.13.4)(focus-trap@7.8.0)(qrcode@1.5.4)(sortablejs@1.15.7)(vue@3.5.26(typescript@5.9.3))
'@xterm/addon-fit':
specifier: ^0.11.0
version: 0.11.0
@@ -179,6 +182,9 @@ importers:
shiki:
specifier: ^3.21.0
version: 3.23.0
sortablejs:
specifier: ^1.15.7
version: 1.15.7
stream-markdown:
specifier: ^0.0.13
version: 0.0.13(shiki@3.23.0)
@@ -246,7 +252,7 @@ importers:
devDependencies:
vitepress:
specifier: ^1.6.0
version: 1.6.4(@algolia/client-search@5.46.2)(@types/node@25.0.3)(axios@1.13.4)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(typescript@5.9.3)
version: 1.6.4(@algolia/client-search@5.46.2)(@types/node@25.0.3)(axios@1.13.4)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(sortablejs@1.15.7)(typescript@5.9.3)
vue:
specifier: ^3.5.0
version: 3.5.26(typescript@5.9.3)
@@ -2391,6 +2397,11 @@ packages:
peerDependencies:
vue: ^3.5.0
'@vueuse/core@14.2.1':
resolution: {integrity: sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==}
peerDependencies:
vue: ^3.5.0
'@vueuse/integrations@12.8.2':
resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==}
peerDependencies:
@@ -2432,12 +2443,57 @@ packages:
universal-cookie:
optional: true
'@vueuse/integrations@14.2.1':
resolution: {integrity: sha512-2LIUpBi/67PoXJGqSDQUF0pgQWpNHh7beiA+KG2AbybcNm+pTGWT6oPGlBgUoDWmYwfeQqM/uzOHqcILpKL7nA==}
peerDependencies:
async-validator: ^4
axios: ^1
change-case: ^5
drauu: ^0.4
focus-trap: ^7 || ^8
fuse.js: ^7
idb-keyval: ^6
jwt-decode: ^4
nprogress: ^0.2
qrcode: ^1.5
sortablejs: ^1
universal-cookie: ^7 || ^8
vue: ^3.5.0
peerDependenciesMeta:
async-validator:
optional: true
axios:
optional: true
change-case:
optional: true
drauu:
optional: true
focus-trap:
optional: true
fuse.js:
optional: true
idb-keyval:
optional: true
jwt-decode:
optional: true
nprogress:
optional: true
qrcode:
optional: true
sortablejs:
optional: true
universal-cookie:
optional: true
'@vueuse/metadata@12.8.2':
resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==}
'@vueuse/metadata@14.1.0':
resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==}
'@vueuse/metadata@14.2.1':
resolution: {integrity: sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==}
'@vueuse/shared@12.8.2':
resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==}
@@ -2446,6 +2502,11 @@ packages:
peerDependencies:
vue: ^3.5.0
'@vueuse/shared@14.2.1':
resolution: {integrity: sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==}
peerDependencies:
vue: ^3.5.0
'@xterm/addon-fit@0.11.0':
resolution: {integrity: sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==}
@@ -4176,6 +4237,9 @@ packages:
resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
engines: {node: '>=20'}
sortablejs@1.15.7:
resolution: {integrity: sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -6790,7 +6854,14 @@ snapshots:
'@vueuse/shared': 14.1.0(vue@3.5.26(typescript@5.9.3))
vue: 3.5.26(typescript@5.9.3)
'@vueuse/integrations@12.8.2(axios@1.13.4)(focus-trap@7.8.0)(qrcode@1.5.4)(typescript@5.9.3)':
'@vueuse/core@14.2.1(vue@3.5.26(typescript@5.9.3))':
dependencies:
'@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 14.2.1
'@vueuse/shared': 14.2.1(vue@3.5.26(typescript@5.9.3))
vue: 3.5.26(typescript@5.9.3)
'@vueuse/integrations@12.8.2(axios@1.13.4)(focus-trap@7.8.0)(qrcode@1.5.4)(sortablejs@1.15.7)(typescript@5.9.3)':
dependencies:
'@vueuse/core': 12.8.2(typescript@5.9.3)
'@vueuse/shared': 12.8.2(typescript@5.9.3)
@@ -6799,13 +6870,27 @@ snapshots:
axios: 1.13.4
focus-trap: 7.8.0
qrcode: 1.5.4
sortablejs: 1.15.7
transitivePeerDependencies:
- typescript
'@vueuse/integrations@14.2.1(axios@1.13.4)(focus-trap@7.8.0)(qrcode@1.5.4)(sortablejs@1.15.7)(vue@3.5.26(typescript@5.9.3))':
dependencies:
'@vueuse/core': 14.2.1(vue@3.5.26(typescript@5.9.3))
'@vueuse/shared': 14.2.1(vue@3.5.26(typescript@5.9.3))
vue: 3.5.26(typescript@5.9.3)
optionalDependencies:
axios: 1.13.4
focus-trap: 7.8.0
qrcode: 1.5.4
sortablejs: 1.15.7
'@vueuse/metadata@12.8.2': {}
'@vueuse/metadata@14.1.0': {}
'@vueuse/metadata@14.2.1': {}
'@vueuse/shared@12.8.2(typescript@5.9.3)':
dependencies:
vue: 3.5.26(typescript@5.9.3)
@@ -6816,6 +6901,10 @@ snapshots:
dependencies:
vue: 3.5.26(typescript@5.9.3)
'@vueuse/shared@14.2.1(vue@3.5.26(typescript@5.9.3))':
dependencies:
vue: 3.5.26(typescript@5.9.3)
'@xterm/addon-fit@0.11.0': {}
'@xterm/addon-serialize@0.14.0': {}
@@ -8687,6 +8776,8 @@ snapshots:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
sortablejs@1.15.7: {}
source-map-js@1.2.1: {}
source-map-support@0.5.21:
@@ -9066,7 +9157,7 @@ snapshots:
tsx: 4.21.0
yaml: 2.8.2
vitepress@1.6.4(@algolia/client-search@5.46.2)(@types/node@25.0.3)(axios@1.13.4)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(typescript@5.9.3):
vitepress@1.6.4(@algolia/client-search@5.46.2)(@types/node@25.0.3)(axios@1.13.4)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(sortablejs@1.15.7)(typescript@5.9.3):
dependencies:
'@docsearch/css': 3.8.2
'@docsearch/js': 3.8.2(@algolia/client-search@5.46.2)(search-insights@2.17.3)
@@ -9079,7 +9170,7 @@ snapshots:
'@vue/devtools-api': 7.7.9
'@vue/shared': 3.5.26
'@vueuse/core': 12.8.2(typescript@5.9.3)
'@vueuse/integrations': 12.8.2(axios@1.13.4)(focus-trap@7.8.0)(qrcode@1.5.4)(typescript@5.9.3)
'@vueuse/integrations': 12.8.2(axios@1.13.4)(focus-trap@7.8.0)(qrcode@1.5.4)(sortablejs@1.15.7)(typescript@5.9.3)
focus-trap: 7.8.0
mark.js: 8.11.1
minisearch: 7.2.0